[译]<<Effective TypeScript>> 技巧54:知道如何迭代object

272 阅读2分钟

本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.

技巧54:知道如何迭代object

下面代码运行没有问题,但是ts报错。为什么?

const obj = {
  one: 'uno',
  two: 'dos',
  three: 'tres',
};
for (const k in obj) {
  const v = obj[k];
         // ~~~~~~ Element implicitly has an 'any' type
         //        because type ... has no index signature
}

查看obj,k的类型就知道:

const obj = { /* ... */ };
// const obj: {
//     one: string;
//     two: string;
//     three: string;
// }
for (const k in obj) {  // const k: string
  // ...
}

k的type是string,而不是'one'|'two'|'three'。所以这里报错了。

我们可以对k的type提前标注:

let k: keyof typeof obj;  // Type is "one" | "two" | "three"
for (k in obj) {
  const v = obj[k];  // OK
}

那为什么k的type是string呢?我们来看一个不同的例子:

interface ABC {
  a: string;
  b: string;
  c: number;
}

function foo(abc: ABC) {
  for (const k in abc) {  // const k: string
    const v = abc[k];
           // ~~~~~~ Element implicitly has an 'any' type
           //        because type 'ABC' has no index signature
  }
}

报错没变,你可以用 keyof 进行修复。但这的报错是有意义的:

const x = {a: 'a', b: 'b', c: 2, d: new Date()};
foo(x);  // OK

foo函数可以传入任意个参数,前提是必须包含a,b,c(见技巧4)。因为这种情况的存在,所以k的类型为「string」。这里使用keyof 反而有坏处:

function foo(abc: ABC) {
  let k: keyof ABC;
  for (k in abc) {  // let k: "a" | "b" | "c"
    const v = abc[k];  // Type is string | number
  }
}

由于可以传入包含a,b,c的任意参数,那么abc[k]的类型是any。那么使用keyof,虽然不报错了,但是abc[k]的范围不准确了。

如果我们想要不报错,同时又诚实的遍历对象,用Object.entries:

function foo(abc: ABC) {
  for (const [k, v] of Object.entries(abc)) {
    k  // Type is string
    v  // Type is any
  }
}

这样虽然对于后续编码不方便,但是至少诚实啊!

你应该意识到『prototype pollution』:

> Object.prototype.z = 3; // Please don't do this!
> const obj = {x: 1, y: 2};
> for (const k in obj) { console.log(k); }
x
y
z

所以当你想要遍历object的所有key,value,可以使用keyof或者Object.entries:

  1. keyof:当你对object更了解,同时能确认不会有多余key。这样就能得到更精准的type
  2. Object.entries:当你对object不那么了解,这是更通用的做法,但是后续编码有麻烦