本文的翻译于<<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:
- keyof:当你对object更了解,同时能确认不会有多余key。这样就能得到更精准的type
- Object.entries:当你对object不那么了解,这是更通用的做法,但是后续编码有麻烦