条件类型和infer
在 TypeScript 中,条件类型(Conditional Types) 是一种根据类型之间的关系动态选择或转换类型的高级特性。它的语法类似于 JavaScript 中的三元表达式(condition ? trueType : falseType),但作用于类型层面,而非值层面。
✅ 核心思想:
“如果类型 A 满足某种条件,则返回类型 B,否则返回类型 C。”
🔧 基本语法
SomeType extends OtherType ? TrueType : FalseType
extends在这里表示 “可赋值性”(assignability),即SomeType是否兼容OtherType。- 整个表达式在编译时求值,不产生任何运行时代码。
✅ 简单示例
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>; // false
type C = IsString<string>; // true
🌟 实际应用场景
1. 泛型中的类型过滤(Extract / Exclude)
TypeScript 内置的 Exclude 和 Extract 就是条件类型的经典应用:
// 排除某些类型
type Exclude<T, U> = T extends U ? never : T;
type T0 = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
// 提取可赋值的类型
type Extract<T, U> = T extends U ? T : never;
type T1 = Extract<'a' | 'b' | 1 | 2, string>; // 'a' | 'b'
2. 处理函数参数或返回值
type ReturnType<T> = T extends (...args: any) => infer R ? R : never;
function add(a: number, b: number): number {
return a + b;
}
type AddResult = ReturnType<typeof add>; // number
🔍 这里用到了
infer关键字 —— 它允许你从条件中“推断”出一个类型变量(如R)。
3. 扁平化嵌套类型(Flattening)
type Flatten<T> = T extends Array<infer Item> ? Item : T;
type T2 = Flatten<string[]>; // string
type T3 = Flatten<number>; // number(不是数组,保持原样)
4. 根据输入类型决定输出类型
type Process<T> = T extends string
? { value: T; type: 'string' }
: T extends number
? { value: T; type: 'number' }
: { value: T; type: 'other' };
type T4 = Process<'hello'>; // { value: 'hello'; type: 'string' }
type T5 = Process<42>; // { value: 42; type: 'number' }
⚠️ 分布式条件类型(Distributive Conditional Types)
当条件类型的左侧是一个裸类型参数(naked type parameter),且传入的是联合类型时,TypeScript 会自动对联合类型的每个成员分别应用条件,再合并结果。
type ToArray<T> = T extends any ? T[] : never;
type StrNum = ToArray<string | number>;
// 等价于:
// (string extends any ? string[] : never) |
// (number extends any ? number[] : never)
// 结果:string[] | number[]
💡 “裸类型参数”指
T直接出现在extends左侧,而不是被包裹(如[T]、Promise<T>等)。
如果你不想分布,可以加一层包装:
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type T6 = ToArrayNonDist<string | number>; // (string | number)[]
✅ 为什么需要条件类型?
- 实现高度复用的工具类型(如
Partial、Required、Pick等底层都依赖它) - 根据输入类型智能推导输出类型
- 构建类型安全的 API,比如 ORM、状态管理库等
📌 总结
| 概念 | 说明 |
|---|---|
| 语法 | T extends U ? X : Y |
| 作用 | 在类型系统中实现“if-else”逻辑 |
| 关键能力 | 类型过滤、类型提取、类型映射、配合 infer 推断 |
| 典型用途 | 工具类型、函数重载模拟、API 类型设计 |
| 注意 | 联合类型会触发“分布式”行为 |
💡 一句话记住:
条件类型 = 类型世界的三元运算符,让类型也能“做判断”。
它是 TypeScript 类型系统图灵完备的关键特性之一,也是构建高级类型工具的基石。
infer 关键字
infer 是 TypeScript 中用于条件类型中的类型推断的关键字。它允许你在泛型条件类型中声明一个待推断的类型变量。
infer 只能在 extends 条件类型的子句中使用,用于提取类型的一部分。
1. 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function foo() {
return 123;
}
type FooReturn = ReturnType<typeof foo>; // number
2. 提取函数参数类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
function bar(x: string, y: number) {}
type BarParams = Parameters<typeof bar>; // [string, number]
3. 提取数组/元组元素类型
type ElementType<T> = T extends (infer U)[] ? U : never;
type NumArray = number[];
type Num = ElementType<NumArray>; // number
type StrArray = string[];
type Str = ElementType<StrArray>; // string
4. 提取 Promise 的解析类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type PromiseNumber = Promise<number>;
type NumberType = UnwrapPromise<PromiseNumber>; // number
type NotPromise = string;
type StringType = UnwrapPromise<NotPromise>; // string
5. 递归解构类型
// 提取多层 Promise
type DeepUnwrapPromise<T> = T extends Promise<infer U> ? DeepUnwrapPromise<U> : T;
type DeepPromise = Promise<Promise<number>>;
type DeepNumber = DeepUnwrapPromise<DeepPromise>; // number
6. 提取构造函数实例类型
type InstanceType<T> = T extends new (...args: any[]) => infer R ? R : any;
class MyClass {}
type MyInstance = InstanceType<typeof MyClass>; // MyClass
7. 联合类型中的推断
// 提取所有函数的返回类型
type AllReturnTypes<T> = T extends any ? (T extends (...args: any[]) => infer R ? R : never) : never;
type Functions = [() => string, () => number];
type Returns = AllReturnTypes<Functions>; // string | number
实际应用示例
1. 路由参数提取
type ExtractRouteParams<T> = T extends `${string}:${infer Param}/${infer Rest}` ? Param | ExtractRouteParams<`${Rest}`> : T extends `${string}:${infer Param}` ? Param : never;
type Route = '/user/:id/post/:postId';
type Params = ExtractRouteParams<Route>; // "id" | "postId"
2. 响应数据处理
type ApiResponse<T> = {
data: T;
status: number;
};
type ExtractApiData<T> = T extends ApiResponse<infer U> ? U : never;
type UserResponse = ApiResponse<{ name: string; age: number }>;
type UserData = ExtractApiData<UserResponse>; // { name: string; age: number }
3. 递归类型处理
// 将嵌套数组展平
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;
type NestedArray = [1, [2, [3, 4]], 5];
type FlatArray = Flatten<NestedArray>; // 1 | 2 | 3 | 4 | 5