打字稿说 array.pop() 即使保证数组包含元素也可能返回 undefined

Wil*_*itz 0 arrays typescript

为什么即使在我确认我正在从非空数组中删除一个元素之后,Typescript 仍然警告我pop()可能会返回undefined

type Person = {
  name: string;
};

const people: Person[] = [{ name: 'Sandra' }];

if (people.length) {
  const { name } = people.pop();
  // TS Error: Property 'name' does not exist on type 'Person | undefined'.
}
Run Code Online (Sandbox Code Playgroud)

即使我做出更明确的保证,我也会得到同样的错误:

if (people.length > 0 && people[people.length - 1] !== undefined){
  const { name } = people.pop();
  // TS Error: Property 'name' does not exist on type 'Person | undefined'.
}
Run Code Online (Sandbox Code Playgroud)

jca*_*alz 6

有关规范答案,请参阅microsoft/TypeScript#30406


对于“为什么编译器认为即使我检查数组也pop()可能返回”的问题,简短的回答是“因为 TypeScript 标准库返回方法调用签名”:undefinedlengthpop()Array<T>T | undefined

interface Array<T> {
  // ... elided
  pop(): T | undefined;
  // ... elided
}
Run Code Online (Sandbox Code Playgroud)

因此,无论何时调用pop()数组,返回值的类型都将包括undefined.


逻辑下一个问题是:“为什么他们不把更好的通话签名这回T如果length不为零,且undefined如果length是零?” 这个问题的答案是“因为检查length一般的数组类型的属性不会改变明显类型的数组,因此呼叫签名可以不出有什么区别。”

例如,您可以添加一些这样的调用签名:

interface Array<T> {
  pop(this: { length: 0 }): undefined;
  pop(this: { 0: T }): T;
}
Run Code Online (Sandbox Code Playgroud)

通过使用this参数,每个调用签名只会在调用方法的数组与指定类型匹配时才会被选中。如果数组有一个lengthof 0,则返回undefined。如果数组T在 key 处有一个 type 元素0,则返回T

这将适用于长度固定且元素索引已知的元组类型:

declare const u: [];
const a = u.pop(); // undefined

declare const v: [1, 2];
const b = v.pop(); // 1 | 2
Run Code Online (Sandbox Code Playgroud)

但是当然,调用pop()元组是一个坏主意,而不是您可能想要的那种东西。如果您有一个可变长度数组并调用pop()它,则不会选择任何调用签名并且您回退到内置的T | undefined,即使您尝试检查length

const w = Math.random() < 0.5 ? [] : ["a"] // string[]
if (w.length) {
  w.pop().toUpperCase(); // error! Object is possibly undefined
}
Run Code Online (Sandbox Code Playgroud)

问题是lengthan的属性Array<T>是 type number,并且没有number要缩小w.length到的“非零”类型。为了支持这种事情,您需要诸如不属于 TypeScript 的否定类型之类的东西。有可能通过足够的编译器工作,有人可以为 TypeScript 中的数组提供足够的结构,这样真实性检查w.length就会将数组的类型缩小到您可以调用的范围pop(),而不必担心undefined退出。

但是它会给编译器和支持这个用例的语言增加大量的复杂性。收益不可能超过成本。


在没有这个的情况下,你可以更容易地跳过length检查并调用pop(),检查它是否undefined存在。这对来说是相同的工作量,因为它只是将检查从调用之前移到调用之后,并且它使编译器的工作更加简单。

此处发布的其他答案建议了此方法和其他解决方法,因此我不会深入研究它们。我的主要观点是语言没有配备允许length检查影响pop(). 或者,正如@RyanCavanaugh(TS 的开发负责人)在 microsoft/TypeScript#30406 中提到的那样

这不是我们能够追踪的

那好吧!

Playground 链接到代码