TypeScript:检查对象属性并缩小类型

327 阅读2分钟

TypeScript的控制流分析让你从一个更广泛的类型缩小到一个更狭窄的类型。

function print(msg: any) {
  if(typeof msg === 'string') {
    // We know msg is a string
    console.log(msg.toUpperCase()) // 👍
  } else if (typeof msg === 'number') {
    // I know msg is a number
    console.log(msg.toFixed(2)) // 👍
  }
}

这是JavaScript中的一个类型安全检查,TypeScript也从中受益。然而,在写这篇文章的时候,有些情况下TypeScript需要我们提供更多的帮助。

让我们假设你有一个JavaScript对象,你不知道某个属性是否存在。该对象可能是anyunknown 。在JavaScript中,你会检查这样的属性。

if(typeof obj === 'object' && 'prop' in obj) {
  //it's safe to access obj.prop
  console.assert(typeof obj.prop !== 'undefined')
  // But TS doesn't know :-(
}

if(typeof obj === 'object' && obj.hasOwnProperty('prop')) {
  //it's safe to access obj.prop
  console.assert(typeof obj.prop !== 'undefined') 
  // But TS doesn't know :-(
}

目前,TypeScript还不能用prop 来扩展obj 的类型。尽管这在JavaScript中是可行的。

然而,我们可以写一个小的辅助函数来获得正确的类型。

function hasOwnProperty<X extends {}, Y extends PropertyKey>
  (obj: X, prop: Y): obj is X & Record<Y, unknown> {
  return obj.hasOwnProperty(prop)
}

如果你不想知道这是如何工作的,复制它并感到高兴。如果你想知道更多,让我们来看看发生了什么。

  1. 我们的hasOwnProperty 函数有两个泛型。
    1. X extends {} 确保我们只在对象上使用这个方法
    2. Y extends PropertyKey 确保键是 。 是一个内置的类型。string | number | symbol PropertyKey
  2. 没有必要明确地定义泛型,它们是通过用法推断出来的。
  3. (obj: X, prop: Y):我们想检查prop 是否是一个属性键的obj
  4. 返回类型是一个类型谓词。如果该方法返回true ,我们可以重新输入我们的任何参数。在这种情况下,我们说我们的obj 是原始对象,其交集类型为Record<Y, unknown> ,最后一块将新发现的属性添加到obj 并将其设置为unknown

在使用中,hasOwnProperty ,就像这样工作。

// person is an object
if(typeof person === 'object' 
  // person = { } & Record<'name', unknown>
  // = { } & { name: 'unknown'}
  && hasOwnProperty(person, 'name') 
  // yes! name now exists in person 👍
  && typeof person.name === 'string' 
  ) {
    // do something with person.name, which is a string
  }