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

TS will not infer possible undefined when destructuring empty array

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

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

console.log(first); // undefined

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

Playground Link

例如,这会导致使用 TSLint 规则 "strict-type-predicates" 的有趣行为,它将 if 语句标记为始终为真,而实际上并非如此。

我是不是遗漏了什么,这是正常现象吗?

TypeScript 4.1 更新

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

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

// --noUncheckedIndexedAccess is enabled

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

console.log(first); // undefined

if (first !== undefined) {
    first.toFixed(); // okay
}

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

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

x.map(n => n.toFixed()); // no error, yay!

所以您可能想要启用该标志。请记住,一些常见的 array/dictionary 操纵技术可能仍然“令人讨厌”,这就是为什么它不属于 --strict 系列的原因。

Playground link to code


原始答案

这是预期的行为。例如,参见 microsoft/TypeScript#13778 for more information. This issue is a request to allow index signature property types to automatically include undefined in their domain, and while the issue is still open, it is fairly clear that it will not be implemented. See this comment

not a design goal of TypeScript(参见列表中的#3)类型系统是完美的或正确的(尽管像我这样的人在考虑太多时会感到心痛;我已经之前开玩笑说要成立一个 TypeScript Unsoundness Support Group 来帮助人们处理这个问题)。相反,在正确性和可用性之间需要权衡。

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


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

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

console.log(first); // undefined

if (first !== undefined) {
    first.toFixed(); // okay
}

但请记住,如果您的用例遵循正常模式,您将 运行 陷入恼人的境地:

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

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

Playground link to code