条件类型

条件类型 (Conditional Types) 是 TypeScript 类型系统中最强大的特性之一。它们允许你根据类型之间的关系,在类型层面实现 if/else 逻辑——根据输入类型动态计算出新的类型。条件类型是 ExcludeReturnTypeParameters 等内置工具类型的实现基础,也是编写高级类型体操和类型安全库的核心工具。

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 自动从匹配位置提取出具体类型。这是实现 ReturnTypeParameters 等工具类型的核心机制。

提取数组元素类型

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[] : neverstring | numberstring[] | number[]
[T] extends [any] ? T[] : neverstring | 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/undefineddistributive + 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] 包装即可。

使用下方搜索框快速查找条件类型模式: