条件类型
条件类型 (Conditional Types) 是 TypeScript 类型系统中最强大的特性之一。它们允许你根据类型之间的关系,在类型层面实现 if/else 逻辑——根据输入类型动态计算出新的类型。条件类型是 Exclude、ReturnType、Parameters 等内置工具类型的实现基础,也是编写高级类型体操和类型安全库的核心工具。
1. 基本语法
条件类型的语法类似三元运算符:T extends U ? X : Y。如果类型 T 可以赋值给类型 U,则结果为 X,否则为 Y。
简单类型检查
type IsString<T> = T extends string ? 'yes' : 'no';
type A = IsString<string>; // 'yes'
type B = IsString<number>; // 'no'
type C = IsString<'hello'>; // 'yes' — string literal extends string
嵌套条件类型
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 T0 = TypeName<string>; // 'string'
type T1 = TypeName<() => void>; // 'function'
type T2 = TypeName<string[]>; // 'object'
类型约束中的条件类型
// Only allow arrays — reject non-array types at compile time
type Flatten<T> = T extends any[] ? T[number] : T;
type Str = Flatten<string[]>; // string
type Num = Flatten<number>; // number (returned as-is)
2. infer 关键字
infer 关键字只能在条件类型的 extends 子句中使用。它声明一个待推断的类型变量,让 TypeScript 自动从匹配位置提取出具体类型。这是实现 ReturnType、Parameters 等工具类型的核心机制。
提取数组元素类型
type ElementType<T> = T extends (infer E)[] ? E : never;
type Str = ElementType<string[]>; // string
type Num = ElementType<number[]>; // number
type Never = ElementType<string>; // never
提取 Promise 内部类型(递归 Awaited)
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number
type C = Awaited<string>; // string (non-Promise passthrough)
提取函数返回类型
type MyReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : never;
type A = MyReturnType<() => string>; // string
type B = MyReturnType<(x: number) => boolean>; // boolean
提取函数参数类型
type MyParameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
type A = MyParameters<(x: string, y: number) => void>; // [x: string, y: number]
提取构造函数参数
type MyConstructorParameters<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: infer P) => any ? P : never;
class User {
constructor(public name: string, public age: number) {}
}
type Args = MyConstructorParameters<typeof User>; // [name: string, age: number]
提取第一个参数类型
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
type A = FirstArg<(x: string, y: number) => void>; // string
type B = FirstArg<() => void>; // unknown
3. 分布式条件类型
当条件类型作用于泛型参数,且该参数是一个裸类型参数(未被包装),传入联合类型时,条件类型会自动对联合类型的每个成员分别求值,最后将结果合并为新的联合类型。这称为分布式行为。
分布式行为示例
type ToArray<T> = T extends any ? T[] : never;
// Union distributes: ToArray<string> | ToArray<number>
type A = ToArray<string | number>; // string[] | number[]
阻止分布——元组包装 [T] extends [any]
将类型参数包装在元组中可以阻止分布式行为:
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
// NOT distributed — treated as a single union
type B = ToArrayNonDist<string | number>; // (string | number)[]
实际对比
| 模式 | 输入 | 结果 |
|---|---|---|
T extends any ? T[] : never | string | number | string[] | number[] |
[T] extends [any] ? T[] : never | string | number | (string | number)[] |
4. 内置工具类型实现原理
TypeScript 的许多内置工具类型底层都使用条件类型实现。理解它们的实现有助于你编写自己的工具类型。
Exclude<T, U>
从联合类型 T 中排除可赋值给 U 的成员。利用分布式条件类型。
// Implementation
type Exclude<T, U> = T extends U ? never : T;
// Usage
type A = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
type B = Exclude<string | number | boolean, string>; // number | boolean
Extract<T, U>
从联合类型 T 中提取可赋值给 U 的成员。与 Exclude 相反。
// Implementation
type Extract<T, U> = T extends U ? T : never;
// Usage
type A = Extract<'a' | 'b' | 'c', 'a' | 'b'>; // 'a' | 'b'
type B = Extract<string | number | (() => void), Function>; // () => void
NonNullable<T>
// Implementation
type NonNullable<T> = T extends null | undefined ? never : T;
// Usage
type A = NonNullable<string | null | undefined>; // string
ReturnType<T>
// Implementation
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
// Usage
type A = ReturnType<() => string>; // string
type B = ReturnType<typeof parseInt>; // number
Parameters<T>
// Implementation
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
// Usage
type A = Parameters<(x: string, y: number) => void>; // [x: string, y: number]
ConstructorParameters<T>
// Implementation
type ConstructorParameters<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: infer P) => any ? P : never;
// Usage
type A = ConstructorParameters<ErrorConstructor>; // [message?: string]
InstanceType<T>
// Implementation
type InstanceType<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: any) => infer R ? R : any;
// Usage
class User { name = ''; }
type A = InstanceType<typeof User>; // User
工具类型总览
| 工具类型 | 作用 | 使用的技术 |
|---|---|---|
Exclude<T, U> | 排除联合成员 | distributive + never |
Extract<T, U> | 提取联合成员 | distributive + never |
NonNullable<T> | 移除 null/undefined | distributive + never |
ReturnType<T> | 提取返回类型 | infer R |
Parameters<T> | 提取参数元组 | infer P |
ConstructorParameters<T> | 提取构造函数参数 | infer P |
InstanceType<T> | 提取实例类型 | infer R |
5. 高级模式
条件类型 + 映射类型:按值类型过滤键
type PickByValue<T, V> = {
[K in keyof T as T[K] extends V ? K : never]: T[K];
};
interface User {
id: number;
name: string;
email: string;
isActive: boolean;
}
type StringFields = PickByValue<User, string>; // { name: string; email: string }
提取可选键与必需键
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];
interface Config { host: string; port: number; debug?: boolean; }
type Opt = OptionalKeys<Config>; // 'debug'
type Req = RequiredKeys<Config>; // 'host' | 'port'
过滤非函数属性键
type NonFunctionKeys<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type DataKeys = NonFunctionKeys<{ id: number; greet(): void; name: string }>;
// 'id' | 'name'
模板字面量 + 条件类型
type EventHandler<T extends string> =
T extends \`on\${infer Event}\` ? Event : never;
type E = EventHandler<'onClick' | 'onFocus' | 'name'>;
// 'Click' | 'Focus' — 'name' filtered out as never
// Reverse: add 'on' prefix
type AddOnPrefix<T extends string> = \`on\${Capitalize<T>}\`;
type H = AddOnPrefix<'click' | 'focus'>; // 'onClick' | 'onFocus'
递归条件类型:DeepReadonly
type DeepReadonly<T> = T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
interface Nested {
a: { b: { c: string } };
}
type R = DeepReadonly<Nested>;
// { readonly a: { readonly b: { readonly c: string } } }
递归条件类型:DeepPartial
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
interface Config {
server: { host: string; port: number };
db: { url: string };
}
type PartialConfig = DeepPartial<Config>;
// All nested fields become optional
键重映射 + as 子句
type Getters<T> = {
[K in keyof T as \`get\${Capitalize<string & K>}\`]: () => T[K];
};
interface Person { name: string; age: number; }
type PersonGetters = Getters<Person>;
// { getName: () => string; getAge: () => number }
6. 实战示例
API 响应类型提取
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
type UnwrapResponse<T> = T extends ApiResponse<infer D> ? D : never;
type UserData = UnwrapResponse<ApiResponse<{ id: number; name: string }>>;
// { id: number; name: string }
// Unwrap array of API responses
type UnwrapAll<T extends any[]> = {
[K in keyof T]: T[K] extends ApiResponse<infer D> ? D : T[K];
};
事件处理器类型推断
type EventMap = {
click: { x: number; y: number };
focus: { target: HTMLElement };
keydown: { key: string; code: string };
};
type EventPayload<T extends keyof EventMap> = EventMap[T];
type Handler<T extends keyof EventMap> = (event: EventPayload<T>) => void;
// Infer event name from handler
type EventFromHandler<T> =
T extends (event: infer E) => void
? E extends EventMap[infer K extends keyof EventMap]
? K
: never
: never;
表单验证类型
type FieldValidator<T> = {
[K in keyof T]: T[K] extends string
? { type: 'text'; minLength?: number; maxLength?: number }
: T[K] extends number
? { type: 'number'; min?: number; max?: number }
: T[K] extends boolean
? { type: 'checkbox' }
: { type: 'custom'; validate: (val: T[K]) => boolean };
};
interface SignupForm {
username: string;
age: number;
agree: boolean;
}
type SignupValidation = FieldValidator<SignupForm>;
// {
// username: { type: 'text'; minLength?: number; maxLength?: number };
// age: { type: 'number'; min?: number; max?: number };
// agree: { type: 'checkbox' };
// }
路径参数提取(路由类型)
type ExtractParams<T extends string> =
T extends \`\${string}:\${infer Param}/\${infer Rest}\`
? { [K in Param | keyof ExtractParams<Rest>]: string }
: T extends \`\${string}:\${infer Param}\`
? { [K in Param]: string }
: {};
type Params = ExtractParams<'/users/:id/posts/:postId'>;
// { id: string; postId: string }
7. 常见问题 (FAQ)
什么时候用条件类型,什么时候用映射类型?
条件类型用于基于类型关系做选择(类似 if/else);映射类型用于遍历对象的键并变换每个属性。两者常常结合使用——例如用映射类型遍历键,同时用条件类型过滤或变换值类型。
为什么我的条件类型返回了 never?
通常有两个原因:(1) 联合类型的所有成员都被过滤了——分布式条件类型对每个成员返回 never,最终 never | never 就是 never;(2) extends 子句中的类型不匹配,走入了 false 分支返回了 never。
如何调试复杂条件类型?
可以将条件类型拆分为多个中间类型,用 type Debug = YourType 查看每步的展开结果。在 VS Code 中,将鼠标悬停在类型别名上可以查看展开后的类型。也可以使用 type Expand<T> = T extends infer U ? { [K in keyof U]: U[K] } : never; 强制展开类型。
infer 可以在 extends 外面使用吗?
不可以。infer 只能在条件类型的 extends 子句中使用。它是类型系统的模式匹配机制,需要条件类型提供匹配上下文。
分布式行为什么时候需要阻止?
当你希望将联合类型作为整体处理而不是逐个处理时,需要阻止分布。例如 IsNever<T>——如果不阻止分布,IsNever<never> 会返回 never 而不是 true。用 [T] extends [never] 包装即可。
8. 代码速查
使用下方搜索框快速查找条件类型模式: