条件类型(Conditional Types)是 TypeScript 类型系统的核心能力之一,语法为 T extends U ? X : Y。它让你可以根据类型关系来决定最终的类型,配合 infer 关键字可以提取类型中的子类型。条件类型是实现 Pick、Exclude、ReturnType 等内置工具类型的底层机制。
// 条件类型基础:T extends U ? X : Y
// 1. 基础语法
type IsString<T> = T extends string ? 'yes' : 'no';
type A = IsString<string>; // 'yes'
type B = IsString<number>; // 'no'
// 2. 分发条件类型(Distributive Conditional Types)
// 当 T 是联合类型时,条件类型会分发到每个成员
type ToArray<T> = T extends any ? T[] : never;
type C = ToArray<string | number>; // string[] | number[]
// 等价于:ToArray<string> | ToArray<number>
// 3. 阻止分发(用元组包裹)
type NoDistribute<T> = [T] extends [any] ? T[] : never;
type D = NoDistribute<string | number>; // (string | number)[]
// 4. infer 关键字 -- 在条件类型中提取类型
// 提取数组元素类型
type ArrayItem<T> = T extends (infer U)[] ? U : never;
type Item = ArrayItem<string[]>; // string
// 提取函数返回值类型
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type Fn = () => string;
type Ret = MyReturnType<Fn>; // string
// 提取 Promise 内层类型
type UnwrapPromise<T> = T extends Promise<infer V> ? V : T;
type P = UnwrapPromise<Promise<number>>; // number
// 5. 实战:根据条件返回不同 Props 类型
type ButtonProps<Variant extends 'primary' | 'danger'> =
Variant extends 'primary'
? { variant: 'primary'; color: string; size: number }
: { variant: 'danger'; confirmText: string; size: number };
type PrimaryBtn = ButtonProps<'primary'>;
// { variant: 'primary'; color: string; size: number }
type DangerBtn = ButtonProps<'danger'>;
// { variant: 'danger'; confirmText: string; size: number }
T extends U ? X : Y 是类型层面的条件判断,类似于值层面的三元表达式,但它是在编译时计算的。Exclude 和 Extract 的基础。infer 只能在条件类型的 extends 分支中使用,用于声明一个待推断的类型变量,TS 会自动从匹配的结构中提取。[T] extends [U])可以阻止分发,让条件类型把联合类型当作整体判断。// 实战:条件类型实现 API 响应类型映射
interface User { id: number; name: string; }
interface Order { id: number; amount: number; }
type ApiEndpoint = '/api/users' | '/api/orders' | '/api/health';
// 根据 URL 推导响应数据类型
type ApiResponse<Url extends string> =
Url extends '/api/users'
? User[]
: Url extends '/api/orders'
? Order[]
: string; // 默认返回类型
type UsersData = ApiResponse<'/api/users'>; // User[]
type OrdersData = ApiResponse<'/api/orders'>; // Order[]
type HealthData = ApiResponse<'/api/health'>; // string