条件类型(Conditional Types)允许根据类型关系选择不同的类型,类似于类型级别的 if-else 语句。
基本语法#
条件类型使用 extends 关键字判断类型关系:
// TypeScript 5.x
// 基本语法:T extends U ? X : Ytype IsString<T> = T extends string ? true : false
type A = IsString<string> // truetype B = IsString<number> // falsetype C = IsString<'hello'> // true
// 实际应用type NonNullable<T> = T extends null | undefined ? never : T
type D = NonNullable<string | null> // stringtype E = NonNullable<number | undefined> // numbertype F = NonNullable<null> // never
// 嵌套条件type TypeName<T> = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends undefined ? 'undefined' : T extends Function ? 'function' : 'object'
type T1 = TypeName<string> // 'string'type T2 = TypeName<number[]> // 'object'type T3 = TypeName<() => void> // 'function'分布式条件类型#
当条件类型作用于联合类型时,会自动分配到每个成员:
// 分布式条件类型type ToArray<T> = T extends any ? T[] : never
type StrArr = ToArray<string> // string[]type NumArr = ToArray<number> // number[]
// 联合类型会被分配type Mixed = ToArray<string | number>// string[] | number[](不是 (string | number)[])
// 过滤联合类型type Filter<T, U> = T extends U ? T : never
type OnlyStrings = Filter<string | number | boolean, string>// string
type OnlyNumbers = Filter<string | number | boolean, number>// number
// 排除类型type MyExclude<T, U> = T extends U ? never : T
type WithoutString = MyExclude<string | number | boolean, string>// number | boolean
// 提取类型type MyExtract<T, U> = T extends U ? T : never
type OnlyStringOrNumber = MyExtract<string | number | boolean, string | number>// string | number禁用分布式行为#
使用元组包装类型参数可以禁用分布式行为:
// 分布式(默认)type Distributed<T> = T extends any ? T[] : nevertype D1 = Distributed<string | number> // string[] | number[]
// 非分布式type NonDistributed<T> = [T] extends [any] ? T[] : nevertype D2 = NonDistributed<string | number> // (string | number)[]
// 检查联合类型type IsUnion<T> = [T] extends [UnionToIntersection<T>] ? false : true
type Check1 = IsUnion<string> // falsetype Check2 = IsUnion<string | number> // true
// 辅助类型type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ( k: infer I) => void ? I : never条件类型约束#
在条件分支中使用类型约束:
// 约束 T 必须有 length 属性type GetLength<T> = T extends { length: infer L } ? L : never
type StrLength = GetLength<string> // numbertype ArrLength = GetLength<number[]> // numbertype NoLength = GetLength<number> // never
// 约束 T 必须是函数type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type FnReturn = GetReturnType<() => string> // stringtype NotFn = GetReturnType<string> // never
// 多层约束type DeepValue<T, K extends string> = K extends keyof T ? T[K] : K extends `${infer First}.${infer Rest}` ? First extends keyof T ? DeepValue<T[First], Rest> : never : never实用条件类型#
// 检查类型相等type Equals<X, Y> = (<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2 ? true : false
type E1 = Equals<string, string> // truetype E2 = Equals<string, number> // falsetype E3 = Equals<{ a: 1 }, { a: 1 }> // true
// 检查是否为 anytype IsAny<T> = 0 extends 1 & T ? true : false
type A1 = IsAny<any> // truetype A2 = IsAny<unknown> // falsetype A3 = IsAny<string> // false
// 检查是否为 nevertype IsNever<T> = [T] extends [never] ? true : false
type N1 = IsNever<never> // truetype N2 = IsNever<string> // false
// 检查是否为数组type IsArray<T> = T extends any[] ? true : false
type Arr1 = IsArray<string[]> // truetype Arr2 = IsArray<string> // false
// 检查是否为元组type IsTuple<T> = T extends readonly any[] ? number extends T['length'] ? false : true : false
type Tup1 = IsTuple<[string, number]> // truetype Tup2 = IsTuple<string[]> // false条件类型与泛型#
// 根据类型选择行为type Flatten<T> = T extends any[] ? T[number] : T
type Flat1 = Flatten<string[]> // stringtype Flat2 = Flatten<string> // stringtype Flat3 = Flatten<number[][]> // number[]
// 深度展平type DeepFlatten<T> = T extends any[] ? DeepFlatten<T[number]> : T
type Deep1 = DeepFlatten<string[][][]> // stringtype Deep2 = DeepFlatten<number> // number
// 条件默认值type WithDefault<T, D> = T extends undefined | null ? D : T
type WD1 = WithDefault<string, 'default'> // stringtype WD2 = WithDefault<undefined, 'default'> // 'default'
// 可选属性检测type OptionalKeys<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never}[keyof T]
interface Example { required: string optional?: number alsoOptional?: boolean}
type OptKeys = OptionalKeys<Example>// 'optional' | 'alsoOptional'实际应用#
Promise 解包#
// 解包 Promisetype UnwrapPromise<T> = T extends Promise<infer U> ? U : T
type P1 = UnwrapPromise<Promise<string>> // stringtype P2 = UnwrapPromise<string> // string
// 深度解包type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T
type A1 = Awaited<Promise<Promise<string>>> // stringtype A2 = Awaited<Promise<number>> // number
// 函数返回值解包type UnwrapReturnType<T extends (...args: any[]) => any> = Awaited< ReturnType<T>>
async function fetchUser() { return { id: 1, name: '张三' }}
type User = UnwrapReturnType<typeof fetchUser>// { id: number; name: string }事件处理#
type EventType = 'click' | 'focus' | 'blur'
type EventPayload<T extends EventType> = T extends 'click' ? { x: number; y: number } : T extends 'focus' ? { target: HTMLElement } : T extends 'blur' ? { relatedTarget: HTMLElement | null } : never
function handleEvent<T extends EventType>(type: T, payload: EventPayload<T>) { switch (type) { case 'click': const clickPayload = payload as EventPayload<'click'> console.log(clickPayload.x, clickPayload.y) break case 'focus': const focusPayload = payload as EventPayload<'focus'> console.log(focusPayload.target) break }}
handleEvent('click', { x: 100, y: 200 })handleEvent('focus', { target: document.body })路由参数#
type ExtractParams<T extends string> = T extends `${string}:${infer Param}/${infer Rest}` ? Param | ExtractParams<Rest> : T extends `${string}:${infer Param}` ? Param : never
type Params1 = ExtractParams<'/users/:id'>// 'id'
type Params2 = ExtractParams<'/posts/:postId/comments/:commentId'>// 'postId' | 'commentId'
// 构建参数对象type RouteParams<T extends string> = { [K in ExtractParams<T>]: string}
type UserParams = RouteParams<'/users/:id'>// { id: string }
type PostParams = RouteParams<'/posts/:postId/comments/:commentId'>// { postId: string; commentId: string }API 响应类型#
type ApiResponse<T, E = Error> = | { success: true; data: T } | { success: false; error: E }
type ExtractData<T> = T extends { success: true; data: infer D } ? D : never
type ExtractError<T> = T extends { success: false; error: infer E } ? E : never
type Response = ApiResponse<{ id: number; name: string }>
type Data = ExtractData<Response>// { id: number; name: string }
type Err = ExtractError<Response>// Error
// 处理函数async function handleResponse<T>(response: ApiResponse<T>): Promise<T> { if (response.success) { return response.data } throw response.error}函数重载选择#
type Overload<T, U> = T extends string ? (value: T) => string : T extends number ? (value: T) => number : (value: T) => U
function process(value: string): stringfunction process(value: number): numberfunction process(value: any): any { return value}
// 类型推断type ProcessString = Overload<string, never> // (value: string) => stringtype ProcessNumber = Overload<number, never> // (value: number) => number常见问题#
🙋 条件类型可以递归吗?#
// TypeScript 支持条件类型递归type Reverse<T extends any[]> = T extends [infer First, ...infer Rest] ? [...Reverse<Rest>, First] : []
type Reversed = Reverse<[1, 2, 3]>// [3, 2, 1]
// 注意:过深的递归会导致错误// TypeScript 有递归深度限制🙋 为什么条件类型结果是 never?#
type Result<T> = T extends string ? T : never
// never 出现的情况:type R1 = Result<number> // never(条件不满足)type R2 = Result<never> // never(never 是空联合类型)
// 联合类型分配后全是 nevertype R3 = Result<number | boolean>// never(两个分支都是 never,合并后还是 never)🙋 如何调试条件类型?#
// 使用辅助类型展开type Debug<T> = { [K in keyof T]: T[K] }
type Complex<T> = T extends object ? { [K in keyof T]: Complex<T[K]> } : T
// 具体化查看type Test = Debug<Complex<{ a: { b: string } }>>// 悬停查看展开后的类型总结#
| 特性 | 语法 | 用途 |
|---|---|---|
| 条件表达式 | T extends U ? X : Y | 类型级别的 if-else |
| 分布式条件 | 联合类型自动分配 | 对每个联合成员应用条件 |
| 禁用分布 | [T] extends [U] | 防止联合类型分配 |
| 类型推断 | infer R | 在条件中提取类型 |
| 嵌套条件 | 多层 ? : | 多条件判断 |
下一篇我们将学习 infer 关键字,深入了解条件类型中的类型推断。