TypeScript Type Guards

February 09, 2020

场景: 当一个变量拥有复合类型, 在某些情况下我们需要对其进行类型断言.

比如: 学生体检时, 除了若干公共体检项目外, 男生还需要检查 A 项目, 女生需要检查 B 项目, 男女生体检结果汇总在一个表中. 在实际操作中, 我们需要根据体检人的 sex(性别)字段来确定展示 A 项目还是 B 项目的体检结果. 代码如下:

interface Boy {
  name: string
  sex: SexEnum
  itemA: string
}
interface Girl {
  name: string
  sex: SexEnum
  itemB: number
}
type Student = Boy | Girl

let s: Student = getOne()
if (s.sex === SexEnum.Boy) {
  console.log(s.itemA) // error!
}

在代码中, 编译器无法确定 sBoy 类型, 似乎只能通过丑陋的 as unknow as Boy@ts-ignore 来绕过错误.

Build-in Type Guards

JS 中内置的 Type Guards 有 typeofinstanceof , 但都无法解决问题:

  • typeof 只能判断基本类型
  • instanceof 只能应用在 class 中, 函数式编程中极少使用 class, 所以作用有限

解决方案: Custom Type Guards

示例代码:

const isBoy = (student: Student): student is Boy => student.sex === SexEnum.Boy

// 另一种写法
const isBoy = (student: Student): student is Boy =>
  (student as Boy).itemA !== undefine

let s: Student = getOne()
if (isBoy(s)) {
  console.log(s.itemA) // no error
}

关键点:

  • 返回值应声明为 variable is Type 的形式, 如  student is Boy
  • 函数体返回一个可以进行类型断言的 bool 值, 如  student.sex === SexEnum.Boy .

A generic(范型) type guard

如果需要编写许多 Type Guards, 使用 custom 的方式将会十分繁复, 不够 DRY.

Generic Type Guard:

export const isOfType = <T>(
  varToBeChecked: any,
  propertyToCheckFor: keyof T
): varToBeChecked is T =>
  (varToBeChecked as T)[propertyToCheckFor] !== undefined;

Use case:

if (isOfType < Car > (item, "turnSteeringWheel")) {
  // 此处item被成功断言为Car
} else {
  console.log("Dude, where's my car?!")
}

问题: 只通过一个属性来断言类型, 有时不够安全. 需要根据实际情况进行修改, 如传入属性列表或其他配置项.


Profile picture

佚树 的个人博客

关于前端、音乐与生活