Рассмотрим этот класс:
const creditRiskRatingKeys = [
'applicant_id',
'is_dirty',
] as const;
const dict = {
applicant_id: 'test_id',
is_dirty: true,
}
class CreditRiskRating {
applicant_id: string = '';
is_dirty: boolean = false;
fillQuestionDictionaryToModel() {
for (const key of creditRiskRatingKeys) {
this[key] = dict[key]; // Type 'string | boolean' is not assignable to type 'never'.
}
}
}
Key
уже keyof CreditRiskRating
, а this
относится к одному и тому же классу. Тогда почему this[key] = dict[key];
должны жаловаться?? Что мне здесь не хватает?
«Ключ уже является ключом КредитРейтинга», верно, и это может быть один из applicant_id
или fillQuestionDictionaryToModel
. Таким образом, значение, которое вы ему присваиваете, должно быть присвоено обоим из них одновременно.
@VLAZ Спасибо. Есть ли способ ограничить CreditRiskRatingKeys
только свойства, а не методы? Как лучше всего с этим справиться.
@Sjoertjuh, спасибо. Есть ли у вас какое-либо решение для этого?
@Sjoertjuh обновил вопрос, и похоже, что это работает не во всех случаях!
Символ never
существует потому, что он является пересечением всех типов свойств (см. ms/TS#30769 ), и поэтому небезопасно назначать что-либо случайно выбранному свойству. Лучший способ справиться с этим, как написано, — изменить его на 'test' as never
вместо 'test' as any
, но это просто обходной путь. Прямо сейчас возникает вопрос о присвоении any
never
, что запрещено. У вас есть некоторый основной вариант использования; не могли бы вы отредактировать, чтобы избежать утверждений типа («приведение») и any
, чтобы мы могли увидеть, как выглядит этот вариант использования?
@jcalz спасибо за комментарий. На самом деле у меня есть словарь вопросов, основанный на некоторых свойствах моего класса. и хочу синхронизировать словарь вопросов и мои свойства в классе. Я обновлю свой вопрос, и мне нужно будет добавить больше зависимостей для моделирования реального варианта использования. Все еще не знаете, почему this[key as keyof this] = 'test' as any;
работает?
потому что keyof this
является неявно универсальным, и поэтому TS не будет с готовностью сворачивать его в never
. Но это, вероятно, выходит за рамки; вопрос SO должен быть узким по охвату для конкретной вещи (я думаю, основной вариант использования здесь), а не для кучи возможно связанных вещей (все проблемы, которые у вас были с различными попытками... у каждого есть причины, почему работает или нет, но будущие читатели вряд ли пробовали то же самое).
@jcalz имеет смысл. обновит вопрос, чтобы сосредоточить его на основном варианте использования
Обновленный вопрос больше не имеет смысла. Вы просите обеспечить типобезопасный способ присвоения строк логическим свойствам. Тогда можно было бы вообще отказаться от использования TypeScript.
@jcalz обновил вопрос. Надеюсь, теперь мой вопрос стал ясен. пожалуйста, дайте мне знать, если нужно больше подробностей
• Вы все еще пытаетесь распределить any
места. Я предлагаю вам прекратить аннотировать вещи и просто позволить TS сделать выводы за вас, чтобы мы могли добраться до стартовой линии. Я бы сказал эта ссылка на игровую площадку показывает минимальный воспроизводимый пример вашей проблемы. Не могли бы вы отредактировать , чтобы использовать текст из этого? • Если да, то я бы сказал, что вы столкнулись с ms/TS#32693 и ms/TS#58905 , и правильный подход показан по этой ссылке на игровую площадку. Это полностью решает вопрос? Если да, то я отвечу; если нет, то чего не хватает?
@jcalz Да, это именно тот случай, я бы тоже был рад избавиться от «любого». Обновил вопрос. Еще раз спасибо
Не могли бы вы также не комментировать creditRiskRatingKeys
и не использовать as const
? Это важно, потому что вы действительно не хотите копировать fillQuestionDictionaryToModel
сам по себе, и TS должен об этом знать. Если бы вы просто скопировали текст из здесь в свой вопрос, это было бы полезно... если вы не хотите этого делать, скажите, пожалуйста, почему, чтобы я мог убедиться, что мой ответ не зависит от того, что вы не хочу делать.
@jcalz Да, для меня это звучит неплохо as const
Спасибо! Я напишу ответ, когда у меня будет возможность.
TypeScript не анализирует типы значений свойств, определенных в вашем CreditRiskRatingKeys
, чтобы попытаться выяснить, назначаете ли вы правильный тип значения данному свойству.
Одним из решений является введение типа Property
, который ограничивает типы значений перечисленных свойств:
export type Property<T, U> = {
[K in keyof T]: T[K] extends U ? K : never
}[keyof T];
export const CreditRiskRatingKeys: Property<CreditRiskRating, string>[] = [
'applicant_id'
] as const;
export class CreditRiskRating {
applicant_id: string = '';
fillQuestionDictionaryToModel() {
let f = this[CreditRiskRatingKeys[0]];
for (const key of CreditRiskRatingKeys) {
this[key] = 'test';
}
}
}
Как вы решите этот случай? Детская площадка
Обновил вопросы, похоже не работает!
Ваш вопрос — движущаяся цель. В его текущей форме вы запрашиваете типобезопасный способ присвоения строки тому, что, возможно, является логическим свойством, что не имеет смысла.
Спасибо за ваше внимание. обновил вопрос. надеюсь теперь понятно!
Тип keyof CreditRiskRating
бывает не только applicant_id
, но и fillQuestionDictionaryToModel
. В JS нет реального различия между методами и свойствами.
Таким образом, хотя CreditRiskRatingKeys
является массивом только со значением applicant_id
, в зависимости от типа это может быть массив с 'applicant_id'
и/или 'fillQuestionDictionaryToModel'
. Таким образом, typeof this[key]
может быть либо строкой, либо функцией () => void
.
Чтобы решить эту проблему, вы можете создать новый тип утилиты PropertyOf
, исключающий методы:
type PropertyOf<T> = {
[K in keyof T]: T[K] extends Function ? never : K
}[keyof T];
и использовать его как
export const CreditRiskRatingKeys: PropertyOf<CreditRiskRating>[] = [
'applicant_id'
];
export class CreditRiskRating {
applicant_id: string = '';
fillQuestionDictionaryToModel() {
for (const key of CreditRiskRatingKeys) {
this[key] = 'test';
}
}
}
Я нашел ошибку здесь
Обновил вопросы, похоже не работает!
Typescript правильно выдает ошибку, поскольку вы присваиваете строковое значение логическому свойству. Тип this[key]
— never
, поскольку для всех свойств класса нет перекрывающегося типа. Что вы пытаетесь достичь?
Я не думаю, что это будет проблемой, поскольку я уже использовал это для any
. И если это так, то как это работает? this[key as keyof this] = 'test' as any;
Вы можете использовать (this[key] as any) = 'test'
. Обратите внимание, что при этом проверка типа удаляется.
В моем реальном сценарии нет кастинга и «любого». я ищу лучшие практики
По сути, вы столкнулись с microsoft/TypeScript#32693 , а также microsoft/TypeScript#58905. См. эти вопросы для получения канонического ответа.
Назначение формы
bar[k] = foo[k]
где Pick<typeof foo, typeof k>
присваивается Pick<typeof bar, typeof k>
(используя тип утилиты Pick, чтобы сказать, что нас интересуют только свойства foo
и bar
, которые существуют в ключах типа k
), почти наверняка безопасно. Но TypeScript не видит этого в большинстве случаев.
Первая проблема возникает, когда k
имеет тип объединения , например "applicant_id" | "is_dirty"
. TypeScript отслеживает только тип k
, а не его идентичность, поэтому, насколько знает TypeScript, вы пишете
bar[k2] = foo[k1]
где k2
и k1
относятся к одному и тому же типу объединения. Это было бы небезопасно, поэтому TypeScript жалуется. Единственный способ, которым такое присвоение может быть безопасным, — это если foo[k1]
имеет тип, который работает для всех возможных k2
, который будет пересечением соответствующих типов свойств, а не объединением:
interface Foo {
a: string,
b: boolean,
}
interface Bar extends Foo {
c: Date
}
function f(foo: Foo, bar: Bar, k: keyof Foo) {
bar[k] = foo[k]; // error!
//~~~~ <-- Type 'string | boolean' is not assignable to type 'never'.
}
Это тема microsoft/TypeScript#32693, и это давняя открытая проблема в TypeScript.
TypeScript разрешает присвоение bar[k]
только в том случае, если это значение одновременно является string
и boolean
. Такого значения нет... другими словами, string & boolean
- это невозможное, никогда не набирайте. По сути, это ошибка, которую вы видите.
Рекомендуемый подход — переключиться с ключей с объединением на универсальные ключи. Таким образом, вместо k
типа MyKeys
у вас должен быть общий тип K extends MyKeys
. И если bar
и foo
— идентичные типы, то это работает:
function g<K extends keyof Foo>(bar: Foo, foo: Foo, k: K) {
bar[k] = foo[k]; // okay, because bar is a Foo
}
Но если эти два типа различны, даже если они эквивалентны во всем, что имеет значение, вы снова получите ошибку:
function h<K extends keyof Foo>(bar: Bar, foo: Foo, k: K) {
bar[k] = foo[k]; // error!
//~~~~ <-- Property 'c' is missing in type 'Foo' but required in type 'Bar'.
}
Это тема microsoft/TypeScript#58905, и это считается ограничением дизайна TypeScript. Обходной путь — синтезировать собственные типы для bar
и foo
так, чтобы они воспринимались как идентичные. Это можно сделать, просто расширив их до эквивалентов типов Pick
. Поскольку каждый Bar
также является Foo
, мы можем просто расширить bar
и оставить foo
в покое:
function i<K extends keyof Foo>(bar: Bar, foo: Foo, k: K) {
const _bar: Foo = bar; // okay
_bar[k] = foo[k]; // okay
}
Итак, нам нужно сделать вещи универсальными, а типы объектов должны быть идентичными. Выполнение того и другого с вашим кодом выглядит так:
type CreditRiskRatingKey = typeof creditRiskRatingKeys[number];
class CreditRiskRating {
applicant_id: string = '';
is_dirty: boolean = false;
fillQuestionDictionaryToModel() {
const thiz: typeof dict = this;
creditRiskRatingKeys.forEach(<K extends CreditRiskRatingKey>(key: K) => {
thiz[key] = dict[key];
});
}
}
Здесь я изменил ваш цикл for...of на общий обратный вызов на forEach() и расширил this
до typeof dict
перед выполнением задания. Теперь компилируется без ошибок. Досадно преодолевать такие препятствия, но это самое близкое к тому, чтобы TypeScript понимал нашу логику. Если вы не хотите этого делать, вы всегда можете использовать утверждения типа например this[key] = dict[key] as never
.
Детская площадка, ссылка на код
Большое спасибо за лучшее объяснение. Только одно, можно ссылку microsoft/TypeScript#32693
Это прямо в верхней части моего ответа.
fillQuestionDictionaryToModel
также является ключомCreditRiskRating
, которому нельзя назначить строку.