Это известная проблема в TypeScript, см. microsoft / TypeScript # 13995 . Проблема заключается в том, что анализ типа на основе потока управления не применяется к параметрам типа generi c.
Если бы key
был объявлен как тип IdentifierEnum
, то проверка if (key === IdentifierEnum.ID1) {...}
сузила бы тип key
внутри блока {...}
до Identifier.ID1
:
const k: IdentifierEnum = key;
if (k === IdentifierEnum.ID1) {
k; // const k: IdentifierEnum.ID1
} else {
k; // const k: IdentifierEnum.ID2
}
Это анализ потока управления. Теперь этого не происходит, когда key
имеет тип c типа K
, но даже если это так, это не поможет:
if (k === IdentifierEnum.ID1) {
return 1; // ERROR!
}
Это потому, что в TypeScript 3.8 в любом случае, даже если значение типа K
сужено, сам тип K
равен , а не . Компилятор никогда не говорит «если key
равно IdentifierEnum.ID1
, то K
равно IdentifierEnum.ID1
». Поэтому вы не можете использовать этот вид реализации на основе потока управления, если хотите, чтобы компилятор проверил безопасность типов для вас.
Возможно, что будущие версии TypeScript каким-то образом улучшат это, но это сложно , В общем, то, что значение x
типа X
может быть сужено до типа Y
, не означает, что сам тип X
может быть сужен. Это очевидно, если у вас есть несколько значений типа X
. Но, во всяком случае, сейчас это что-то, чтобы обойти.
Вы уже изучили и были недовольны менее безопасными для типов способами сделать это: вводить утверждения и подписи с перегрузкой, поэтому я не буду писать, как бы вы это реализовали.
Единственный способ сделать что-то относительно безопасное для типов - это отказаться от анализа потока управления и вместо этого использовать операцию индексации. Компилятор достаточно умен, чтобы понять, что если у вас есть значение t
типа T
и значение k
типа K extends keyof T
, то значение t[k]
будет иметь тип T[K]
. В вашем случае T
- это DataType
. Таким образом, вам нужно значение этого типа для индексации:
class LoadingService {
loadData<K extends IdentifierEnum>(key: K): DataType[K] {
return {
get [IdentifierEnum.ID1]() { return 1; },
get [IdentifierEnum.ID2]() { return "" }
}[key]; // okay
}
}
Приведенный выше тип проверяет. Обратите внимание, что я реализовал свойства как getters . Вам не нужно делать это; Вы могли бы просто написать:
return {
[IdentifierEnum.ID1]: 1,
[IdentifierEnum.ID2]: ""
}[key];
, но версия геттера позволяет вам делать больше произвольных вычислений и знать, что будет оцениваться только тот, который соответствует key
.
Хорошо, надеюсь, это поможет; удачи!
Детская площадка ссылка на код