TypeScript 类型收窄技巧:让联合类型替你挡住错误
TypeScript 的价值不只是给变量标注类型,而是把“不可能状态”尽量排除在编译阶段。类型收窄能让一份联合类型随着控制流逐步变具体,减少强制断言和运行时分支遗漏。
用可辨识联合表达状态
不要用多个可选字段表达互斥状态:
type RequestState<T> =
| {status: 'idle'}
| {status: 'loading'}
| {status: 'success'; data: T}
| {status: 'error'; message: string}
function render<T>(state: RequestState<T>) {
switch (state.status) {
case 'success':
return state.data
case 'error':
return state.message
default:
return null
}
}
这样 success 状态一定有 data,error 状态一定有 message,不用到处写可选链。
把外部输入当作 unknown
API、LocalStorage、PostMessage 和第三方 SDK 返回的数据都不可信。any 会关闭检查,unknown 会迫使调用方验证:
function isUser(value: unknown): value is User {
if (!value || typeof value !== 'object') return false
const record = value as Record<string, unknown>
return typeof record.id === 'string' && typeof record.name === 'string'
}
大型项目可用 Schema 校验库在运行时解析,并从 Schema 推导 TypeScript 类型,避免“类型声明正确、真实 JSON 错误”。
善用内置收窄
typeof:适合基本类型。instanceof:适合同一运行时中的类实例。in:按属性区分对象联合。- 相等判断:让两个变量相互收窄。
- 自定义 Type Predicate:复用领域校验。
注意 typeof null === 'object',也不要用真值判断把空字符串或数字 0 当成缺失值。
用 never 检查分支完整性
function assertNever(value: never): never {
throw new Error(`Unhandled state: ${JSON.stringify(value)}`)
}
function color(status: RequestState<unknown>['status']) {
switch (status) {
case 'idle': return 'gray'
case 'loading': return 'blue'
case 'success': return 'green'
case 'error': return 'red'
default: return assertNever(status)
}
}
未来新增状态但忘记更新这里,编译器会直接报错。这比一个静默的 default 更安全。
satisfies 比粗暴断言更可靠
as SomeType 可能掩盖错误,satisfies 会检查结构,同时保留对象字面量的精确类型:
const routes = {
home: '/',
article: '/articles',
} satisfies Record<string, `/${string}`>
实战规则
- 领域状态优先使用可辨识联合,不堆布尔变量。
- 系统边界统一接收
unknown,校验后再进入业务层。 - 尽量消除
as和非空断言!,每个断言都应能解释依据。 - 公共函数返回明确联合,让调用方处理失败路径。
- Switch 对封闭状态使用
never做穷尽检查。
类型设计不是追求复杂技巧,而是让错误状态难以构造,让正确用法成为最省事的用法。

评论
0 条讨论