强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

TypeScript 开发指南 / 11 - 条件类型

条件类型

条件类型(Conditional Types)是 TypeScript 类型系统中最强大的特性之一,它允许根据类型条件进行类型推断和变换。

基本语法

条件类型的语法类似三元表达式:

// 基本条件类型
type IsString<T> = T extends string ? "yes" : "no";

type A = IsString<string>;  // "yes"
type B = IsString<number>;  // "no"
type C = IsString<"hello">; // "yes"

条件类型的结构

T extends U ? X : Y
│            │    │
│            │    └─ 否则返回 Y
│            └─ 那么返回 X
└─ 如果 T 可以赋值给 U

分布式条件类型(Distributive Conditional Types)

当条件类型作用于联合类型时,会自动分发:

type ToArray<T> = T extends any ? T[] : never;

// 对联合类型的每个成员分别应用
type Result = ToArray<string | number>;
// 等价于 ToArray<string> | ToArray<number>
// = string[] | number[]

// 而不是 (string | number)[]

禁止分发

// 使用 [] 包裹禁止分发
type ToArrayNonDist<T> = [T] extends [any] ? T[] : never;

type Result2 = ToArrayNonDist<string | number>;
// (string | number)[]

infer 关键字

infer 用于在条件类型中声明待推断的类型变量:

推断函数返回值类型

type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type A = MyReturnType<() => string>;           // string
type B = MyReturnType<(x: number) => boolean>; // boolean
type C = MyReturnType<string>;                  // never

推断函数参数类型

type MyParameters<T> = T extends (...args: infer P) => any ? P : never;

type A = MyParameters<(a: string, b: number) => void>;
// [a: string, b: number]

推断 Promise 内部类型

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

// 递归解包
type DeepUnwrapPromise<T> = T extends Promise<infer U>
  ? DeepUnwrapPromise<U>
  : T;

type D = DeepUnwrapPromise<Promise<Promise<Promise<string>>>>; // string

推断数组元素类型

type ArrayElement<T> = T extends (infer E)[] ? E : never;

type A = ArrayElement<string[]>;              // string
type B = ArrayElement<(string | number)[]>;   // string | number
type C = ArrayElement<[string, number]>;      // string | number

推断元组首尾元素

type Head<T extends any[]> = T extends [infer H, ...any[]] ? H : never;
type Tail<T extends any[]> = T extends [any, ...infer T] ? T : never;
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;

type A = Head<[string, number, boolean]>; // string
type B = Tail<[string, number, boolean]>; // [number, boolean]
type C = Last<[string, number, boolean]>; // boolean

推断构造函数实例类型

type InstanceOf<T> = T extends new (...args: any[]) => infer I ? I : never;

class User {
  name: string = "";
}

type UserInstance = InstanceOf<typeof User>; // User

复杂推断示例

推断模板字面量类型

type ExtractRouteParams<T extends string> =
  T extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ExtractRouteParams<Rest>
    : T extends `${string}:${infer Param}`
    ? Param
    : never;

type Params = ExtractRouteParams<"/users/:userId/posts/:postId">;
// "userId" | "postId"

推断对象值类型

type ValueOf<T> = T[keyof T];

type User = {
  name: string;
  age: number;
  email: string;
};

type UserValue = ValueOf<User>; // string | number

递归类型

递归深度限制

// 递归 DeepPartial
type DeepPartial<T> = T extends object
  ? { [P in keyof T]?: DeepPartial<T[P]> }
  : T;

interface Config {
  a: {
    b: {
      c: string;
    };
    d: number;
  };
  e: boolean;
}

type PartialConfig = DeepPartial<Config>;
// {
//   a?: {
//     b?: { c?: string };
//     d?: number;
//   };
//   e?: boolean;
// }

递归 DeepReadonly

type DeepReadonly<T> = T extends (infer U)[]
  ? ReadonlyArray<DeepReadonly<U>>
  : T extends object
  ? { readonly [P in keyof T]: DeepReadonly<T[P]> }
  : T;

递归元组操作

// 反转元组
type Reverse<T extends any[]> = T extends [infer H, ...infer Rest]
  ? [...Reverse<Rest>, H]
  : [];

type A = Reverse<[1, 2, 3]>; // [3, 2, 1]

// 连接元组
type Concat<A extends any[], B extends any[]> = [...A, ...B];
type B = Concat<[1, 2], [3, 4]>; // [1, 2, 3, 4]

// Flatten 元组
type Flatten<T extends any[]> = T extends [infer H, ...infer Rest]
  ? H extends any[]
    ? [...Flatten<H>, ...Flatten<Rest>]
    : [H, ...Flatten<Rest>]
  : [];

type C = Flatten<[[1, 2], [3, [4, 5]]]>; // [1, 2, 3, 4, 5]

递归字符串操作

// 字符串转联合类型
type StringToUnion<S extends string> =
  S extends `${infer C}${infer Rest}`
    ? C | StringToUnion<Rest>
    : never;

type Letters = StringToUnion<"hello">; // "h" | "e" | "l" | "o"

// 驼峰转连字符
type CamelToKebab<S extends string> =
  S extends `${infer C}${infer Rest}`
    ? Rest extends `${infer R}${string}`
      ? R extends Uppercase<R>
        ? `${Lowercase<C>}-${CamelToKebab<Rest>}`
        : `${C}${CamelToKebab<Rest>}`
      : C
    : S;

type Kebab = CamelToKebab<"backgroundColor">; // "background-color"

递归数字类型

// 构建元组表示数字
type BuildTuple<N extends number, T extends any[] = []> =
  T["length"] extends N ? T : BuildTuple<N, [...T, any]>;

// 加法
type Add<A extends number, B extends number> = [
  ...BuildTuple<A>,
  ...BuildTuple<B>
]["length"] & number;

type Sum = Add<2, 3>; // 5

条件类型与映射类型结合

// 根据条件过滤属性
type FilterByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};

interface User {
  id: number;
  name: string;
  age: number;
  email: string;
  active: boolean;
}

type StringProps = FilterByType<User, string>;
// { name: string; email: string }

type NumberProps = FilterByType<User, number>;
// { id: number; age: number }

// 提取可选属性的键
type OptionalKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];

// 提取必选属性的键
type RequiredKeys<T> = Exclude<keyof T, OptionalKeys<T>>;

条件类型推断的实战应用

事件处理类型

interface EventMap {
  click: { x: number; y: number };
  change: { value: string };
  submit: { data: Record<string, any> };
}

type EventHandler<K extends keyof EventMap> = (
  event: EventMap[K]
) => void;

type EventHandlers = {
  [K in keyof EventMap]: EventHandler<K>;
};

// 或使用条件类型
type EventHandlerFor<T> = T extends keyof EventMap
  ? (event: EventMap[T]) => void
  : never;

API 路由类型推断

// 路由定义
interface ApiRoutes {
  "/users": {
    GET: { response: User[] };
    POST: { body: CreateUserDto; response: User };
  };
  "/users/:id": {
    GET: { response: User };
    PUT: { body: UpdateUserDto; response: User };
    DELETE: { response: void };
  };
}

// 推断响应类型
type ApiResponse<
  Path extends keyof ApiRoutes,
  Method extends keyof ApiRoutes[Path]
> = ApiRoutes[Path][Method] extends { response: infer R } ? R : never;

type UsersResponse = ApiResponse<"/users", "GET">; // User[]
type UserResponse = ApiResponse<"/users/:id", "GET">; // User

业务场景:类型安全的 ORM 查询

// 字段类型映射
interface ModelFields {
  User: {
    id: number;
    name: string;
    email: string;
    age: number;
    active: boolean;
    createdAt: Date;
  };
}

// 查询条件类型
type WhereCondition<T> = {
  [K in keyof T]?: T[K] | T[K][] | {
    eq?: T[K];
    in?: T[K][];
    gt?: T[K] extends number ? T[K] : never;
    lt?: T[K] extends number ? T[K] : never;
    like?: T[K] extends string ? string : never;
  };
};

// 类型安全的查询
type UserWhere = WhereCondition<ModelFields["User"]>;

const where: UserWhere = {
  name: { like: "%Alice%" },
  age: { gt: 18 },
  active: true
};

注意事项

  1. 分布式条件类型:联合类型会自动分发,需要时用 [] 包裹禁止
  2. infer 只能在条件类型的 extends 子句中使用
  3. 递归类型注意深度限制——TypeScript 有递归深度限制(约 50 层)
  4. 条件类型可以用于创建复杂的类型变换——但不要过度复杂化
  5. 联合类型的分发顺序:先分发再求值

扩展阅读