条件类型是 TypeScript 类型系统的"if-else"。
条件类型基础
type IsString<T> = T extends string ? 'yes' : 'no'
type A = IsString<string> // 'yes'
type B = IsString<number> // 'no'
条件类型的语法:T extends U ? X : Y
- 如果 T 可以赋值给 U,类型是 X
- 否则,类型是 Y
infer:从已有类型结构中提取信息
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type A = ReturnType<() => string> // string
type B = ReturnType<(x: number) => boolean> // boolean
infer R 表示"推断出函数返回类型,命名为 R"。
这不是为了炫技,而是为了从已有的类型结构中提取信息。比如:
// 从 Promise 中提取类型
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T
type A = Awaited<Promise<string>> // string
type B = Awaited<Promise<Promise<number>>> // number
Discriminated Unions:用类型表达状态机
这才是条件类型真正重要的应用。
// ❌ 不好的设计:多个布尔值
type State = {
isLoading: boolean
isError: boolean
data?: User
error?: Error
}
// 问题:isLoading 和 isError 可以同时为 true,不可能的状态
// ✅ 好的设计:discriminated union
type State =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: User }
| { status: 'error'; error: Error }
status 是判别属性(discriminant),它的值决定了其他属性是否存在。
收窄与不可能状态
function UserProfile({ state }: { state: State }) {
switch (state.status) {
case 'idle':
return <div>请输入查询</div>
case 'loading':
return <div>加载中...</div>
case 'success':
return <div>{state.data.name}</div> // ✅ TypeScript 知道这里有 data
case 'error':
return <div>{state.error.message}</div> // ✅ TypeScript 知道这里有 error
}
}
TypeScript 的收窄(narrowing):在 switch 的每个分支里,TypeScript 知道 state 的具体类型。
Exhaustiveness Checking
function assertNever(value: never): never {
throw new Error(`Unexpected value: ${value}`)
}
function UserProfile({ state }: { state: State }) {
switch (state.status) {
case 'idle':
return <div>请输入查询</div>
case 'loading':
return <div>加载中...</div>
case 'success':
return <div>{state.data.name}</div>
case 'error':
return <div>{state.error.message}</div>
default:
return assertNever(state) // ✅ 如果遗漏了某个分支,编译报错
}
}
如果以后给 State 添加了新分支,但忘记处理,TypeScript 会报错。
核心洞察
TypeScript 不只是描述数据结构,还可以描述系统允许存在的状态空间。
不可能的状态无法表达,编译时就会报错。
这就是类型驱动的状态建模:
用类型约束系统可以处于哪些状态,让非法状态在编译时就被发现。