Основная проблема заключается в том, что общие параметры типа c не сужаются с помощью анализа потока управления, который является довольно давней открытой проблемой в TypeScript, для получения дополнительной информации см. microsoft / TypeScript # 13995 .
Когда вы проверяете type
в операторе switch
/ case
, компилятор может сузить тип переменной type
до литерального типа "array"
, но он не сужает введите параметр T
до "array"
. И поэтому он не может проверить, что безопасно присвоить значение any[]
для типа AvailableTypes[T]
. Компилятору придется выполнить некоторый анализ, которого он в настоящее время не выполняет, например, «хорошо, если type === "array"
, и мы вывели T
из типа type
, то внутри этого блока case
мы можем сузить T
к "array"
, и, следовательно, тип this.value
равен AvailableTypes["array"]
, иначе, any[]
, и поэтому можно назначить []
для него. " Но этого не происходит.
Та же проблема вызывает ошибку "value
не определено назначен". У компилятора нет средств, чтобы увидеть, что switch
/ case
исчерпывает все возможности для T
, поскольку здесь не выполняется анализ потока управления.
Самый простой обходной путь здесь нужно использовать утверждения типа , чтобы сообщить компилятору, что вы знаете, что делаете, поскольку он не может это проверить.
Чтобы справиться с проблемой исчерпанности, вы можете либо создать default
кейс с бросками, например:
class Wrapper<T extends keyof AvailableTypes> {
private readonly type: T;
private readonly value: AvailableTypes[T];
constructor(type: T) {
this.type = type;
switch (type) {
case 'array':
this.value = [] as AvailableTypes[T]; // assert
break;
case 'string':
this.value = '' as AvailableTypes[T]; // assert
break;
case 'object':
this.value = {} as AvailableTypes[T]; // assert
break;
default:
throw new Error("HOW DID THIS HAPPEN"); // exhaustive
}
}
}
, либо увеличить type
с T
до keyof AvailableTypes
, который позволит компилятору выполнить анализ потока управления, необходимый для понимания того, что случаи являются исчерпывающими:
class Wrapper<T extends keyof AvailableTypes> {
private readonly type: T;
private readonly value: AvailableTypes[T];
constructor(type: T) {
this.type = type;
const _type: keyof AvailableTypes = type; // widen to concrete type
switch (_type) {
case 'array':
this.value = [] as AvailableTypes[T]; // assert
break;
case 'string':
this.value = '' as AvailableTypes[T]; // assert
break;
case 'object':
this.value = {} as AvailableTypes[T]; // assert
break;
}
}
}
Другой обходной путь (, упомянутый в комментарии человеком, который реализовал изменение надежности ) заключается в использовании того факта, что если у вас есть значение t
типа T
и клавиша k
типа K extends keyof T
, то будет виден тип t[k]
компилятором как T[K]
. Так что, если мы можем сделать правильный AvailableTypes
, мы можем просто индексировать его с помощью type
. Может быть так:
class Wrapper<T extends keyof AvailableTypes> {
private readonly type: T
private readonly value: AvailableTypes[T];
constructor(type: T) {
this.type = type;
const initValues: AvailableTypes = {
array: [],
string: "",
object: {}
};
this.value = initValues[type];
}
}
Это намного более хороший способ для go, чем утверждения типа и switch
, и достаточно безопасен для загрузки. Так что я бы go с этим решением, если ваш вариант использования не запрещает его.
Хорошо, надеюсь, что один из тех помогает. Удачи!
Ссылка на игровую площадку с кодом
Обновление: дискриминированный союз вместо универсальных c классов