Object.keys 类型收窄

问题

平时在写代码时,经常会遇到遍历一个对象的所有属性,我们可以使用Object.keys来拿到对象所有的key,然后在通过myObject[key]来获取属性的值。但是如果在TS中直接这样写的话,就会有类型错误。

const myObject = {
  a: 1,
  b: 2,
  c: 3
}
Object.keys(myObject).forEach(key => {
// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type
  console.log(myObject[key])
})

在线试一试

那么一般我们遇到这种问题该怎么解决呢?

剧透预警线

解答

一般遇到这种问题,最直接的做法就是就是把类型校验错误的地方改正,由于Object.keys返回的类型是string[],所以我们代码中的key的类型是string,但是myObjectkey类型却是"a"|"b"|"c"。所以我们只要把key的类型重新as一下就行了:

const myObject = {
  a: 1,
  b: 2,
  c: 3
}
Object.keys(myObject).forEach(key => {
  console.log(myObject[key as keyof typeof MyObject])
})

但是这样会有不好的地方就是,我们所有用Object.keys的地方都需要手动这样as一下很麻烦,而且我们应该少用as来进行强制类型断言。

所以我们可以自己声明一个方法,对这个方法设置返回的类型:

const myObject = {
  a: 1,
  b: 2,
  c: 3
}

const keys = Object.keys as <T>(o: T) => (keyof T)[];

keys(myObject).forEach(key => {
  console.log(myObject[key])
})

这样的话就不报类型不匹配的错误了。 还有另一种方式可以直接修改内置的Object.keys的方法的返回类型:

interface ObjectConstructor {
  keys<T>(o: T): ReadonlyArray<keyof T>;
}
const myObject = {
  a: 1,
  b: 2,
  c: 3
};
Object.keys(myObject).forEach(key => {
  console.log(myObject[key]);
})

但是这种做法一般是不推荐的,因为内置的实现中Object.keys返回string[]是有一定的道理的,如果我们强制修改了它的返回值类型,在一些场景中就会有问题。 比如:

const keys = Object.keys as <T>(o: T) => (keyof T)[];

keys(MyObject).forEach(key => {
  console.log(MyObject[key])
})

const a = {x: 1, y: 2}
const b: {x: number} = a

const k1 = keys(b) //"x"[]
const k2 = Object.keys(b) //string[]

这个时候 k1 的类型是有问题的。