场景: 当一个变量拥有复合类型, 在某些情况下我们需要对其进行类型断言.
比如: 学生体检时, 除了若干公共体检项目外, 男生还需要检查 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!
}
在代码中, 编译器无法确定 s 为 Boy 类型, 似乎只能通过丑陋的 as unknow as Boy 或 @ts-ignore 来绕过错误.
Build-in Type Guards
JS 中内置的 Type Guards 有 typeof 和 instanceof , 但都无法解决问题:
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?!")
}
问题: 只通过一个属性来断言类型, 有时不够安全. 需要根据实际情况进行修改, 如传入属性列表或其他配置项.