Skip to content
第 104 / 250 章前端⏱ 10 分钟阅读

第 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>;      // true

1.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>;  // User

3.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>;  // string

3.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>>>;  // number

3.4 提取数组元素

typescript
type ElementOf<T> = T extends Array<infer E> ? E : never;

type T1 = ElementOf<string[]>;            // string
type T2 = ElementOf<Array<number>>;       // number

3.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]>;  // 2

3.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>;  // string

4.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>;  // () => void

4.3 NonNullable<T>

typescript
type NonNullable<T> = T extends null | undefined ? never : T;

type T1 = NonNullable<string | null>;        // string
type T2 = NonNullable<number | undefined>;   // number

4.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>;  // User

4.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>;  // User

4.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>;      // false

5.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] 包裹
inferextends 子句中推断子类型
提取位置数组元素、函数参数/返回值、Promise 等
内置工具Exclude/Extract/ReturnType/Parameters/Awaited

动手练习

  1. 条件类型:写一个 IsNever<T> 类型,判断 T 是否是 never
  2. infer 练习:写一个 MyAwaited<T> 类型,递归解开 Promise
  3. 分布式:写一个 UnionToIntersection<T>,把联合变成交叉
  4. 字符串模板:写一个 Replace<S, From, To>,把字符串 S 中的 From 替换成 To

推荐阅读


下一章第 105 章:工具类型(Utility Types)

本站基于 VitePress 构建 · 由 Codebook 团队维护