大家好,我是 ConardLi。
大家好,最近 TypeScript 发布了 5.4 Beta 版本,其中包含了一些值得关注的新特性以及一些 Break Change,我们一起来看下吧:
优化闭包中的类型收窄
“类型收窄” 在 TypeScript
中是一个常见的类型推断过程,基于我们可能进行的某些检查或条件,TypeScript
能够自动推断出变量的具体类型,这就使得该变量的类型范围被“缩小”或者说“窄化”。这项特性可以帮助我们编写更加准确、安全的代码。
function uppercaseStrings(x: string | number) {
if (typeof x === "string") {
// TypeScript在这里知道'x'是一个'string'
return x.toUpperCase();
}
}
一个常见的痛点是,我们在闭包函数里是感知不到这些被收窄后的类型的。
代码语言:javascript复制function getUrls(url: string | URL, names: string[]) {
if (typeof url === "string") {
url = new URL(url);
}
return names.map(name => {
url.searchParams.set("name", name)
// ~~~~~~~~~~~~
// error!
// Property 'searchParams' does not exist on type 'string | URL'.
return url.toString();
});
}
在代码中,我们首先检查了 url
的类型,如果 url
是字符串(即 typeof url === "string"
),我们把它转化为 URL
对象。在这个语句块中,TypeScript
能够理解 url
已经不再是一个字符串,而是一个 URL
对象,因此我们可以在后面调用 URL
对象的 searchParams
属性。
可是,在数组的 map
方法中,TypeScript
不能保证 url
的类型已经窄化为 URL
,因为他无法确定在回调函数被执行的当下,url
是否仍然是 URL
对象,这是因为在函数的闭包中,变量可能会被之后的代码改变,因此 TS
认为这种类型窄化是不安全的。
但其实在这个例子中,这个箭头函数肯定是在对 url
进行类型变更后被创建的,并且对 url
的类型变更是最后的赋值操作,所以 url
在这个函数中的类型就是我们赋值的类型。
因此,TypeScript 5.4
做了改进,当参数和 let
变量在非提升函数中使用时,类型检查器将查找最后一个赋值点。如果找到一个,TypeScript
可以从包含该函数的外部安全地窄化,那上面的代码示例就可以正常工作了。
但是还需要注意一点,如果我们是在嵌套函数中的任何地方对变量进行了赋值,类型收窄还是不起作用的。这是因为我们没有办法确保是否会在以后调用该函数。
代码语言:javascript复制function printValueLater(value: string | undefined) {
if (value === undefined) {
value = "missing!";
}
setTimeout(() => {
// Modifying 'value', even in a way that shouldn't affect
// its type, will invalidate type refinements in closures.
value = value;
}, 500);
setTimeout(() => {
console.log(value.toUpperCase());
// ~~~~~
// error! 'value' is possibly 'undefined'.
}, 1000);
}
Github
上有个 Issue
就是讨论这个问题的,感兴趣可以看看:https://github.com/microsoft/TypeScript/pull/56908
我之前的文章:什么是鸭子