Skip to content

TypeScript 类型收窄 详解

TypeScript 在分支内收窄类型的各种方式,按技术分组:内置守卫、自定义谓词、断言函数、可辨识联合、相等性与真值流,以及别名与延迟收窄。每行把技术与单行示例配对。

收窄(narrowing)是 TypeScript 把宽类型在代码分支内变成更窄类型的方式,从而证明属性访问是安全的。每一次检查(typeof、in、谓词、可辨识标签)都在告诉编译器:从这里开始,这个值更具体了。下面的技术就是常用的工作集。

参考表格 · 23 条目
23 of 23 rows
内置守卫
收窄原始类型:string、number、boolean 等。if (typeof x === 'string') x.toUpperCase()
收窄为类或可构造的实例。if (e instanceof Error) e.message
通过检查值上是否存在某属性来收窄。if ('id' in obj) obj.id
把任意值收窄为数组;把联合收窄到其数组成员。if (Array.isArray(x)) x.map(f)
自定义守卫
返回值能收窄参数的谓词函数。function isUser(x): x is User { return !!x.id }
谓词可以收窄为多个类型的联合,而不只是单个类型。function isKey(x): x is string | number
交叉类型谓词保留输入类型并向其添加字段。function hasId(x): x is x & { id: string }
断言函数,除非 x 是该类型,否则抛出异常。function assertUser(x): asserts x is User
可辨识联合
通过共享的字面量字段用 switch 或 if 收窄联合。type E = { kind: 'a'; a: number } | { kind: 'b'; b: string }
每个 case 收窄到联合的一个成员。switch (u.kind) { case 'a': u.a }
当某属性只存在于一个成员时,收窄无标签的联合。if ('b' in u) u.b
每个 case 是一个条件;命中的 case 收窄该值。switch (true) { case typeof x === 'string': }
default 分支赋值给 never,证明 switch 覆盖了所有情况。default: { const x: never = u }
相等性与流程
相等检查后移除 null 和 undefined。if (x === null) return; x.toFixed()
移除假值(0、''、null、undefined、NaN)。if (!x) return; // x is defined here
链式属性为真值时证明基值存在。if (user?.active) user.name
非空断言不做检查就移除 null 和 undefined。x!.toFixed(2)
明确赋值断言:声明 x 在被读取之前会先赋值。let name!: string; init()
收窄在分支内以及提前 return 之后都有效。if (!arr) return; arr.map(...)
别名与延迟
保存检查结果的 const 布尔值在它被测试的任何地方都能收窄。const isStr = typeof x === 'string'; if (isStr) …
const 变量在捕获它的闭包内保持收窄。const s = get(); if (s) use(() => s.length)
自 TS 5.4 起,let 或参数在最后一次赋值之后在闭包内保持收窄。let n: string | null = get(); if (n) use(() => n.length)
unknown 在守卫把它收窄为真实类型之前无法使用。if (typeof u === 'string') u.length