这是 Typescript 4.3 中添加的
上下文缩小泛型类型值 的副作用,该特性由
microsoft/TypeScript#43183 实现。长期存在着一个开放问题
microsoft/TypeScript#13995,其中
控制流分析无法像处理特定类型的值那样缩小
被约束为
联合类型的泛型类型值。
例如,以下代码总是有效的,其中
x
是特定联合类型
string | number
的变量:
function checkSpecific(x: string | number) {
if (typeof x !== "string") {
console.log(x.toFixed(2));
}
}
这里的事实是,
typeof x !== "string"
允许编译器将
x
缩小为
number
并看到它具有
toFixed()
方法。但在 TypeScript 4.3 之前,下面的代码不起作用,其中
x
的类型为
T extends string | number
:
function checkGeneric<T extends string | number>(x: T) {
if (typeof x !== "string") {
console.log(x.toFixed(2));
}
}
编译器顽固地拒绝看到
typeof x !== "string"
对
x
的类型有任何影响。编译器
不能假设类型参数
T
本身应该被缩小。毕竟,也许
T
确实是完整的联合类型
string | number
(例如
checkGeneric(Math.random()<0.5 ? "abc" : 123)
),所以缩小
T
不正确。但是编写上述代码的人并不关心缩小
T
,他们希望将
x
从
T
缩小为
number
。
因此,使用TypeScript 4.3,在某些情况下,当给出泛型类型的值时,其中泛型类型参数被限制为联合类型,则这些值将首先扩展到约束,并且然后可以进行缩小:
function checkGeneric<T extends string | number>(x: T) {
if (typeof x !== "string") {
console.log(x.toFixed(2));
}
}
编译器决定将
x
的类型视为特定的类型
string | number
,而不是泛型类型
T
。一旦这样做了,
typeof x !== "string"
就可以将
x
缩小为所需的
number
。
不幸的是,在你的代码中,相同的分析会导致意想不到的行为。在 TypeScript 4.3 之前,不会出现错误:
function foo<T extends A | B>(target: T[]): T[] {
const res = [];
for (const e of target) {
res.push(e);
}
return res
}
变量
res
被认为是“自动类型”或“隐式
any
”变量,因为编译器无法使用其初始化程序推断类型;它需要等待看看你对它做了什么,然后根据此“演化”类型。对于像
res
这样的数组,这是在
microsoft/TypeScript#11432 中实现的。
在 TypeScript 4.3 之前,当你调用
res.push(e)
时,编译器会看到
e
的类型为
T
,因此
res
现在演化为类型
T[]
,然后
return res
就可以了。
但从 TypeScript 4.3 开始,这种情况已经改变:
function foo<T extends A | B>(target: T[]): T[] {
const res = [];
for (const e of target) {
res.push(e);
}
return res
}
变量
res
仍然是自动类型的,并且在调用
res.push(e)
时进行演化。但是由于值
e
是一个通用类型,受到联合约束,编译器使用其新的行为,将
e
从
T
扩展到限制范围内的
A | B
。这意味着
res
的类型是
(A | B)[]
,并且您会收到错误消息。由于您在此代码中从未尝试将
e
从
A | B
缩小为
A
或
B
,因此控制流分析的增强支持对您的目的完全没有用处。
哦,好吧。
请注意,旧行为和新行为都不是“错误”的;类型为
T
且
T extends XXX
的值可以安全地扩展为
XXX
。只是在某些情况下,
T
比
XXX
更或更少有用。TypeScript 4.3 中添加的启发式算法改善了许多情况,但不幸的是在其他情况下使情况变得更糟。如果有人提出了这个问题,我会很感兴趣看看会发生什么,但我不会把它称为一个 bug。
Playground链接到代码pre-ms/TS#43183
(注:该文本包含一个指向某个网站的链接,具体内容需要根据上下文判断翻译)
这句话的意思是“
Playground link to code post-ms/TS#43183
”,其中包含一个链接,指向代码发布的游乐场。具体内容无法确定,需要上下文来进一步理解。