解构空数组时,TS 不会推断可能的未定义

Eck*_*sDy 12 destructuring type-safety typescript typescript-typings

当查看解构元素的推断类型时,它将假设数组永远不为空。

const x: number[] = [];
const [first] = x; // first inferred as number

console.log(first); // undefined

if (first !== undefined) {
    // ...
}

Run Code Online (Sandbox Code Playgroud)

游乐场链接

例如,这会导致 TSLint 规则“strict-type-predicates”出现一个有趣的行为,它将把 if 语句标记为始终为真,而实际上并非如此。

我是否遗漏了一些东西,这是正常行为?

jca*_*alz 11

TypeScript 4.1 的更新

undefined由于下面列出的原因,它仍然是未包含在索引签名属性域中的预期行为。然而,由于这是一个经常要求的功能,TypeScript 团队(有点不情愿?)态度软化并添加了--noUncheckedIndexedAccess编译器标志。如果您启用它(默认情况下它既不启用,也不包含在--strict编译器标志中,因此您需要显式执行此操作),您将开始获得您可能想要的行为。

请注意,这实际上与添加您自己并不完全相同undefined。索引访问将添加undefined

// --noUncheckedIndexedAccess is enabled

const x: number[] = [];
const [first] = x; // number | undefined

console.log(first); // undefined

if (first !== undefined) {
    first.toFixed(); // okay
}
Run Code Online (Sandbox Code Playgroud)

但是for..of循环和数组函数式编程方法仍然会表现得好像undefined不可能(即:就好像稀疏数组不存在一样):

for (const n of x) {
    n.toFixed(); // no error, yay!
}

x.map(n => n.toFixed()); // no error, yay!
Run Code Online (Sandbox Code Playgroud)

所以您可能想启用该标志。请记住,一些常见的数组/字典操作技术可能仍然“烦人”,这就是为什么它不属于该技术家族的原因--strict

Playground 代码链接


原答案

这是预期的行为。有关更多信息,请参阅microsoft/TypeScript#13778 。此问题是允许索引签名属性类型自动包含undefined在其域中的请求,虽然该问题仍然悬而未决,但很明显它不会被实现。例如,请参阅此评论。

TypeScript 的设计目标(参见列表中的#3)并不是让类型系统完美健全或正确(尽管像我这样的人在考虑太多时会感到心痛;我之前开玩笑说要开始TypeScript 不健全支持小组来帮助人们处理这个问题)。相反,需要在正确性和可用性之间进行权衡。

语言维护者指出,有很多现实世界的代码会索引到数组中,而不检查undefined各处可能的值,并且强制执行此检查会将简单的 for 循环变成执行检查或使用类型断言的繁琐练习。问题是(请参阅此评论)编译器无法轻松区分数组类型的安全索引和不安全索引之间的区别。因此,要么编译器假设该属性不会undefined,并且当该假设错误时,编译器会产生误报,就像现在发生的那样......或者,假设该属性可能是undefined,并且在索引操作时编译器会产生误报实际上是安全的。语言维护者的论点是,这种误报经常发生,以至于开发人员会要求自己完全忽略这些错误,从而使其与当前情况一样无用,同时更烦人。所以他们会保持现在的样子。


如果您愿意,您始终可以undefined自己添加元素类型,假设此类问题更有可能在您的用例中出现:

const x: (number | undefined)[] = [];
const [first] = x; // number | undefined

console.log(first); // undefined

if (first !== undefined) {
    first.toFixed(); // okay
}
Run Code Online (Sandbox Code Playgroud)

但请记住,如果您的用例遵循正常模式,您将遇到恼人的情况:

for (const n of x) {
    n.toFixed(); // error, annoying
    if (typeof n !== "undefined") n.toFixed(); // okay
    n!.toFixed(); // okay
}

x.map(n => n.toFixed()); // error, annoying
x.filter((n): n is number => typeof n !== "undefined").filter(n => n.toFixed()); // okay
x.map(n => n!.toFixed()); // okay
Run Code Online (Sandbox Code Playgroud)

好的,希望有帮助;祝你好运!

Playground 代码链接

  • https://github.com/microsoft/TypeScript/issues/13778#issuecomment-691331991 最近实施 (2认同)