typescript pop()可能会返回undefined,即使保证数组包含元素

hm2xizp9  于 2022-11-30  发布在  TypeScript
关注(0)|答案(6)|浏览(251)

为什么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'.
}

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

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

zpgglvta1#

请参阅microsoft/TypeScript#30406以获取规范答案。
对于问题“为什么编译器认为即使我检查数组的lengthpop()也可能返回undefined“,简短的回答是“因为TypeScript标准库对Array<T>上的pop()方法的调用签名返回T | undefined“:

interface Array<T> {
  // ... elided
  pop(): T | undefined;
  // ... elided
}

因此,无论何时对数组调用pop(),返回值的类型都将包括undefined
下一个合乎逻辑的问题是“为什么他们不做更好的调用签名,如果length为非零,则返回T,如果length为零,则返回undefined?”对 that 的回答是“因为检查通用数组类型的length属性不会改变数组的表观类型,因此调用签名无法区分”。
例如,您可以添加一些调用签名,如下所示:

interface Array<T> {
  pop(this: { length: 0 }): undefined;
  pop(this: { 0: T }): T;
}

通过使用this参数,只有在调用方法得数组与指定得类型匹配时,才会选择每个调用签名.如果数组具有0length,则返回undefined.如果数组在键0处具有T类型得元素,则返回T.
对于长度固定且元素索引已知的元组类型,这将非常有效:

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

declare const v: [1, 2];
const b = v.pop(); // 1 | 2

当然,在元组上调用pop()不是一个好主意,也不是你想要的。如果你有一个变长数组,并在它上面调用pop(),两个调用签名都不会被选中,你会回到内置的T | undefined,即使你试图检查length

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

问题是Array<T>length属性是number类型,并且没有“非零number“类型来缩小w.length的范围。为了支持这类事情,您需要像negated types这样的东西,它们不是TypeScript的一部分。通过充分的编译器工作,有人可以在TypeScript中为数组提供足够的结构,这样,对w.length的真实性检查将把数组的类型缩小到可以调用pop()的类型,而不必担心undefined会被取出。
但是这会给编译器和支持这种用例的语言增加大量的复杂性,好处不太可能超过代价。
如果没有这个,那么跳过length检查,直接调用pop(),检查它是否是undefined就容易多了。这对 you 来说是同样的工作量,因为它只是把检查从调用之前移到调用之后,这使得 * 编译器 * 的工作更简单。
这里发布的其他答案建议了这个和其他的解决方案,所以我就不深入讨论了。我的主要观点是,该语言只是不允许length检查影响pop()的行为。或者,正如@RyanCavanaugh(TS的开发负责人)在microsoft/TypeScript#30406中所写的,
这不是我们能追踪到的
好吧!
Playground代码链接

wecizke3

wecizke32#

你可以这样做。

type Person = {
  name: string;
};

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

if (people.length) {
  const nextPerson = people.pop() ;
   if(nextPerson){
    const {name} =nextPerson;
   }
  
}

解决方案-2(弹出后!)

type Person = {
      name: string;
    };
    
    const people: Person[] = [{ name: 'Sandra' }];
    
    if (people.length) {
        const {name}=people.pop()!;
        console.log(name);
    
      
    }
nzk0hqpo

nzk0hqpo3#

可以使用as关键字Assertpop()返回的字符串如下:

if (people.length) {
  const { name } = people.pop() as string;
}

这告诉Typescript pop()正在返回一个字符串,而不是undefined。

igetnqfo

igetnqfo4#

为什么不直接用pop()来检查值的存在性呢?当数组为空时,pop方法返回undefined

type Person = {
    name: string;
};

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

let person = people.pop();
if (person) {
    const { name } = person;
}
eni9jsuy

eni9jsuy5#

您可以使用“as”关键字或“!”来Assertarray.pop()不返回非空值或非未定义值。

// We are telling the compiler that the returned value will always be a string
if (people.length) {
  const { name } = people.pop() as string;
}

// We are telling compiler that the value will never be null or undefined 
if (people.length) {
  const { name } = people.pop()!;
}

for more information

mzsu5hc0

mzsu5hc06#

type Person = {
  name: string;
};

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

if (people.length) {
    let popped = people.pop();
    console.log(popped);
}

输出量:

[LOG]: {
  "name": "Sandra"
}

它解决了这个问题,然后你就可以得到名称了。但是可能是typescript无法将返回值Map到对象{ name }

相关问题