第 104 章:条件类型(Conditional Types)
学习目标
- 掌握条件类型的基本语法
- 学会
infer关键字的高级用法 - 理解分布式条件类型
- 掌握内置条件类型(Exclude / Extract / ReturnType 等)
一、条件类型基础
条件类型的语法类似三元表达式,但作用于类型层面。
1.1 基本语法
typescript
T extends U ? X : Y含义:如果 T 可以赋值给 U,则结果是 X,否则是 Y。
1.2 第一个示例
typescript
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<number>; // false
type C = IsString<string>; // true1.3 实战:类型分支
typescript
type TypeName<T> =
T extends string ? 'string' :
T extends number ? 'number' :
T extends boolean ? 'boolean' :
T extends undefined ? 'undefined' :
T extends null ? 'null' :
'object';
type T1 = TypeName<string>; // 'string'
type T2 = TypeName<number>; // 'number'
type T3 = TypeName<Date>; // 'object'二、分布式条件类型(Distributive)
当 T 是裸类型参数(直接是泛型参数,没有被包装),条件类型会自动分发到联合类型上。
2.1 基础分布
typescript
type ToArray<T> = T extends any ? T[] : never;
// 联合类型会逐个应用
type Result = ToArray<string | number>;
// 等价于:
// (string extends any ? string[] : never) | (number extends any ? number[] : never)
// = string[] | number[]2.2 实战:过滤联合类型
typescript
// 提取 string 类型
type FilterString<T> = T extends string ? T : never;
type Mixed = string | number | boolean | string;
type OnlyString = FilterString<Mixed>; // string
// Exclude 本质就是分布式条件类型
type MyExclude<T, U> = T extends U ? never : T;
type Result = MyExclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'2.3 阻止分布
typescript
// 用 [T] 包裹,把联合类型变成元组,失去"裸"特性
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;
type A = ToArrayNonDist<string | number>;
// 不会分布,结果是 (string | number)[]三、infer 关键字详解
infer 在条件类型的 extends 子句中声明一个待推断的类型变量。
3.1 提取函数返回值
typescript
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function fetchUser(): User { /* ... */ }
type User = MyReturnType<typeof fetchUser>; // User3.2 提取函数参数
typescript
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
function greet(name: string, age: number) { }
type GreetParams = MyParameters<typeof greet>; // [name: string, age: number]
// 提取第一个参数
type FirstParameter<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
type First = FirstParameter<typeof greet>; // string3.3 提取 Promise 内部类型
typescript
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type A = UnwrapPromise<Promise<string>>; // string
type B = UnwrapPromise<Promise<Promise<number>>>; // Promise<number>(单层)
type C = UnwrapPromise<number>; // number(不是 Promise)
// 递归解开多层 Promise
type DeepUnwrapPromise<T> = T extends Promise<infer U> ? DeepUnwrapPromise<U> : T;
type D = DeepUnwrapPromise<Promise<Promise<number>>>; // number3.4 提取数组元素
typescript
type ElementOf<T> = T extends Array<infer E> ? E : never;
type T1 = ElementOf<string[]>; // string
type T2 = ElementOf<Array<number>>; // number3.5 提取元组类型
typescript
// 第一个元素
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;
type T1 = First<[number, string]>; // number
// 最后一个元素
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;
type T2 = Last<[number, string, boolean]>; // boolean
// 元组长度
type Length<T extends any[]> = T extends { length: infer L } ? L : never;
type T3 = Length<[number, string]>; // 23.6 多个 infer
typescript
// 提取函数签名
type FunctionSignature<T> =
T extends (a: infer A, b: infer B) => infer R
? { args: [A, B]; return: R }
: never;
function fn(x: number, y: string): boolean { return true; }
type Sig = FunctionSignature<typeof fn>;
// { args: [number, string]; return: boolean }四、内置条件类型
4.1 Exclude<T, U>
typescript
type Exclude<T, U> = T extends U ? never : T;
// 排除指定成员
type T1 = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'
// 排除多种类型
type T2 = Exclude<string | number | boolean, number | boolean>; // string4.2 Extract<T, U>
typescript
type Extract<T, U> = T extends U ? T : never;
// 提取交集
type T1 = Extract<'a' | 'b' | 'c', 'a' | 'b'>; // 'a' | 'b'
// 提取兼容类型
type T2 = Extract<string | number | (() => void), Function>; // () => void4.3 NonNullable<T>
typescript
type NonNullable<T> = T extends null | undefined ? never : T;
type T1 = NonNullable<string | null>; // string
type T2 = NonNullable<number | undefined>; // number4.4 ReturnType<T>
typescript
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
function fetchUser(id: number): User { /* ... */ }
type T = ReturnType<typeof fetchUser>; // User4.5 Parameters<T>
typescript
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
function fn(a: string, b: number) { }
type T = Parameters<typeof fn>; // [a: string, b: number]
// 提取构造函数参数
type ConstructorParameters<T> =
T extends new (...args: infer P) => any ? P : never;4.6 InstanceType<T>
typescript
type InstanceType<T extends new (...args: any) => any> =
T extends new (...args: any) => infer R ? R : any;
class User { name = 'Tom' }
type T = InstanceType<typeof User>; // User4.7 Awaited<T>(TS 4.5+)
typescript
type Awaited<T> =
T extends null | undefined ? T :
T extends object & { then(onfulfilled: infer F, ...args: infer _) } ?
F extends ((value: infer V, ...args: infer _) => any) ?
Awaited<V> : never : T;
type T1 = Awaited<Promise<string>>; // string
type T2 = Awaited<Promise<Promise<number>>>; // number五、条件类型实战
5.1 区分函数和对象
typescript
type IsFunction<T> = T extends (...args: any[]) => any ? true : false;
type A = IsFunction<() => void>; // true
type B = IsFunction<string>; // false5.2 API 响应包装
typescript
type ApiResult<T> = T extends Error
? { ok: false; error: T }
: { ok: true; data: T };
function fetch<T>(): ApiResult<T> {
// ...
}
const r1 = fetch<User>(); // { ok: true; data: User } | { ok: false; error: Error }5.3 路径参数提取
typescript
// 从路径中提取参数
type ExtractParams<T extends string> =
T extends `${string}:${infer P}/${infer Rest}`
? P | ExtractParams<Rest>
: T extends `${string}:${infer P}`
? P
: never;
type Path = '/users/:id/posts/:postId';
type Params = ExtractParams<Path>; // 'id' | 'postId'5.4 函数柯里化
typescript
type Curry<T extends (...args: any[]) => any> =
T extends (first: infer A, ...rest: infer R) => infer U
? R extends []
? (a: A) => U
: (a: A) => Curry<(...args: R) => U>
: T;
function add(a: number, b: number, c: number): number {
return a + b + c;
}
const curried = (add as any) as Curry<typeof add>;
// curried(1)(2)(3) - 全部柯里化5.5 区分数组和对象
typescript
type IsArray<T> = T extends any[] ? true : false;
type A = IsArray<number[]>; // true
type B = IsArray<{ length: 0 }>; // false六、条件类型的限制与技巧
6.1 配合类型谓词
typescript
type Filter<T, U> = T extends U ? T : never;
type Status = 'idle' | 'loading' | 'success' | 'error';
// 提取"已完成"的状态
type FinishedStatus = Filter<Status, 'success' | 'error'>; // 'success' | 'error'6.2 配合映射类型
typescript
type Optional<T> = {
[K in keyof T]?: T[K] extends infer U ? U : never;
};
// 看起来绕,但实际上是 T[K] = T[K] 的"显式 infer"写法6.3 递归条件类型
typescript
// 类型层面的递归
type DeepReadonly<T> = T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
interface Nested {
a: number;
b: {
c: string;
d: {
e: boolean;
};
};
}
type Frozen = DeepReadonly<Nested>;
// 每一层都是 readonly七、常见错误
7.1 忘记分布式特性
typescript
// 想把整个联合都变成数组,结果被分布
type Wrap<T> = T extends any ? T[] : never;
type Result = Wrap<string | number>;
// 结果: string[] | number[]
// 想要 (string | number)[]
type WrapArray<T> = [T] extends [any] ? T[] : never;
type Result2 = WrapArray<string | number>;
// 结果: (string | number)[]7.2 infer 在错误的位置
typescript
// ❌ 不能在 : 右侧直接 infer
// type T<U> = infer U extends string ? U : never; // ❌
// ✅ infer 只能在 extends 子句中
type T<U> = U extends infer X ? X : never; // ✅ 但这样 infer 没有意义7.3 递归没有终止条件
typescript
// ❌ 无限递归,TS 会报错
// type Loop<T> = Loop<T>;
// ✅ 必须有终止条件
type Recurse<T> = T extends string ? T : Recurse<...>;八、类型体操练习
8.1 实现 MyPick
typescript
type MyPick<T, K extends keyof T> = {
[P in K]: T[P];
};
interface User { id: number; name: string; email: string }
type PickedUser = MyPick<User, 'id' | 'name'>;
// { id: number; name: string }8.2 实现 TypeOf
typescript
type MyTypeOf<T> =
T extends string ? 'string' :
T extends number ? 'number' :
T extends boolean ? 'boolean' :
T extends undefined ? 'undefined' :
T extends null ? 'null' :
T extends Function ? 'function' :
'object';
type T = MyTypeOf<() => void>; // 'function'8.3 实现 If<C, T, F>
typescript
type If<C extends boolean, T, F> = C extends true ? T : F;
type A = If<true, 'yes', 'no'>; // 'yes'
type B = If<false, 'yes', 'no'>; // 'no'8.4 实现 TupleToUnion
typescript
type TupleToUnion<T extends any[]> = T[number];
type Colors = ['red', 'green', 'blue'];
type Color = TupleToUnion<Colors>; // 'red' | 'green' | 'blue'九、本章小结
| 要点 | 关键 |
|---|---|
| 条件语法 | T extends U ? X : Y |
| 分布式 | 裸类型参数自动分布到联合 |
| 阻止分布 | 用 [T] 包裹 |
| infer | 在 extends 子句中推断子类型 |
| 提取位置 | 数组元素、函数参数/返回值、Promise 等 |
| 内置工具 | Exclude/Extract/ReturnType/Parameters/Awaited |
动手练习
- 条件类型:写一个
IsNever<T>类型,判断 T 是否是 never - infer 练习:写一个
MyAwaited<T>类型,递归解开 Promise - 分布式:写一个
UnionToIntersection<T>,把联合变成交叉 - 字符串模板:写一个
Replace<S, From, To>,把字符串 S 中的 From 替换成 To
推荐阅读
- 📖 TypeScript Handbook - Conditional Types — 条件类型官方文档
- 📖 TypeScript Handbook - infer — infer 详解
- 🌐 Type Challenges - Conditional — 练习题