这可能是缺少的功能。
当您检查(x === "A") 时,它在值x 的类型上充当type guard,导致它通过control flow analysis 从联合类型"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 中的基本问题,并且已经提出了多个相关问题,并提出了有关如何在应该安全地进行此类缩小的情况下处理它的建议。不过,到目前为止,还没有任何东西成为这种语言。
不过,这并不是完整的故事;有人可以反驳:好吧,也许你不能将 type 参数 K 从 K extends Union 缩小到 K extends "A",但是肯定 您可以将值 key 的类型从K 缩小到"A" 或K & "A"(intersection type),这将使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 为字符串或数字文字,但 从不 产生交集类型。我已经 asked before, see microsoft/TypeScript#21732, 为 a in b 类型守卫产生了一些交叉点,但它还没有实现。所以这可能是两个缺少的功能:
- 没有缩小泛型类型参数,并且
- 没有内置类型防护装置缩小到交叉路口。
因此,变通方法:显然,对于这个示例来说,最简单的方法就是将泛型类型变量的值重新分配给联合类型变量:
const k: Union = key;
if (k === "A") {
Global[k].toFixed();
}
或者,您可以使用type assertion,如上面的as (K & "A") 或只是as "A":
if (key === "A") {
Global[key as (K & "A")].toFixed(); // okay
Global[key as "A"].toFixed(); // okay
}
或者,如果这种情况经常发生,您可以编写自己的user-defined type guard 函数,因为用户定义的类型保护确实会在后续控制流的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