在TypeScript中,类型缩小(Type Narrowing)是一个过程,编译器“理解”在特定代码块中变量显然具有更具体的类型,基于条件。
典型的缩小技巧:
typeof 操作符进行检查:function example(x: number | string) { if (typeof x === 'string') { // x: string 在这里 return x.toUpperCase(); } else { // x: number 在这里 return x.toFixed(2); } }
instanceof 进行检查(适用于类):if (dateObj instanceof Date) { // dateObj: Date }
null 和 undefined:function print(value?: string) { if (value != null) { // value: string console.log(value.length); } }
type Pet = { kind: 'dog'; woof: () => void } | { kind: 'cat'; meow: () => void }; function sound(pet: Pet) { if (pet.kind === 'dog') { pet.woof(); } else { pet.meow(); } }
TypeScript也支持用户自定义的类型谓词:
function isString(x: unknown): x is string { return typeof x === 'string'; }
缩小使类型检查更安全,代码更可靠。
可以通过普通比较(例如
==/===)来保证类型缩小吗,它总是有效吗?
答案: 不。在所有情况下,TypeScript并不总是通过简单的比较理解类型,特别是当比较过于“模糊”或通过间接变量/属性时。通常需要使用显式机制(typeof,instanceof,判别属性和类型保护)。
示例:
function foo(x: number | string | null) { if (x) { // x: string | number,null已经不可能,但不会缩小到具体类型 } }
故事
在一个大型TypeScript项目中,类型条件 user.role == 'admin' 并没有缩小数据类型,依然需要编写存在性检查。开发人员低估了缩小规则,导致出现“无法读取未定义的属性...”的错误。
故事
在一个移动应用程序中,函数接受对象或字符串。通过变更类型的间接函数调用,未能进行缩小,并且在某些设备上调用不存在于字符串上的方法时发生崩溃。稀有情况的测试失败。
故事
在将代码从JavaScript迁移到TypeScript时,没有实现自己的类型保护函数,假设对属性的检查总会缩小类型。结果,具有可选字段的复杂对象表现不正确,并在运行时出现不可预测的数据访问错误。