Вероятно, это недостающая функция.
Когда вы отмечаете (x === "A")
, он действует как type guard для типа значения x
, в результате чего он сужается от типа объединения "A"|"B"
до просто "A"
через управление анализом потока.
К сожалению, общие параметры типа в TypeScript не могут быть сужены с помощью анализа потока управления; см. microsoft/TypeScript#24085 для получения дополнительной информации. Так, например, проверка (key === "A")
не сужает тип K
до "A"
. Такое ограничение имеет смысл, когда у вас есть несколько значений одного и того же универсального типа:
function foo<K extends Union>(key: K, x: Union, key2: K) {
if (key2 === "A") {
Global[key].toFixed(); // error!
}
}
Очевидно, что проверка значения key2
не должна влиять на тип key
, поэтому компилятор консервативно не считает, что K
следует сужать. Это основная проблема в Microsoft/TypeScript#13995, и было поднято несколько связанных с ней проблем с предложениями о том, как справиться с ней в случаях, когда такое сужение должно быть безопасным. Однако до сих пор ничего не вошло в язык.
Однако на самом деле это не вся история; можно возразить: хорошо, возможно вы не можете сузить параметр типа K
с K extends Union
до K extends "A"
, но конечно вы можете сузить тип значения key
от K
до "A"
или K & "A"
(тип пересечения), что сделает Global[key].toFixed()
успешным:
if (key === "A") {
Global[key as (K & "A")].toFixed(); // okay
}
И у меня нет хорошего ответа на это прямо сейчас. Большинство вопросов, которые я видел по этому поводу, в конечном итоге были переданы в microsoft/TypeScript#13995. ????♂️
Самое близкое, что я могу прийти к полному ответу, это то, что кажется, что использование встроенных средств защиты типа, таких как a === b
или typeof a === "string"
или a instanceof B
или a in b
, заканчивается только фильтрацией объединений или, возможно, сужением string
или number
до строковых или числовых литералов, но никогда создает тип пересечения. Я спрашивал ранее, см. microsoft/TypeScript#21732, для защиты типа a in b
для создания некоторых пересечений, но это не было реализовано. Таким образом, это могут быть две недостающие функции:
- отсутствие сужения параметров универсального типа и
- нет ограждения встроенного типа, сужающегося к перекресткам.
Итак, обходные пути: очевидно, что самый простой для этого примера — просто переназначить значение переменной общего типа переменной типа объединения:
const k: Union = key;
if (k === "A") {
Global[k].toFixed();
}
Или вы можете использовать утверждение типа, как в выше as (K & "A")
или просто as "A"
:
if (key === "A") {
Global[key as (K & "A")].toFixed(); // okay
Global[key as "A"].toFixed(); // okay
}
Или, если это происходит часто, вы можете написать свои собственные защита определяемого пользователем типа, так как защита определяемого пользователем типа делает создание пересечений в ветви true
последующего потока управления:
const isEq =
<T extends string | number | boolean>(v: any, c: T): v is T => v === c;
if (isEq(key, "A")) {
Global[key].toFixed(); // okay, key is now of type K & "A";
}
Playground link to code
person
jcalz
schedule
07.02.2020