前置知识: TypeScript

条件类型分发

00:00
3 min Advanced 2026/6/14

分布式条件类型与控制

概述

条件类型分发(Distributive Conditional Type)是 TypeScript 条件类型在处理联合类型时的特殊行为:当条件类型的检查参数是裸类型参数(naked type parameter)时,TypeScript 会将联合类型的每个成员分别代入条件类型,然后将结果合并为新的联合类型。理解分发行为对于正确使用条件类型至关重要,因为有时需要利用分发来实现类型过滤和映射,有时又需要阻止分发以保持联合类型的完整性。

基础概念

分布式条件类型:当条件类型的形式为 T extends U ? X : Y,且 T 是裸类型参数(没有被元组、函数等包裹)时,TypeScript 会对 T 的每个联合成员分别求值。

类型参数直接出现在条件类型 extends 左侧的类型参数,没有被其他类型构造器包裹。例如 T extends U 中的 T 是裸类型参数,而 [T] extends [U] 中的 T 不是。

阻止分发:将类型参数元组包裹([T] extends [U])可以阻止分发行为,使联合类型作为整体参与条件判断。

never 的特殊行为:当分发目标类型是 never 时,结果也是 never,因为空集的任何映射结果都是空集。

快速上手

分发行为演示

// 分布式条件类型:对联合类型每个成员分别求值
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>;
// 等价于:ToArray<string> | ToArray<number>
// 结果:string[] | number[]

// 对比:阻止分发
type ToArrayNoDistribute<T> = [T] extends [any] ? T[] : never;
type Result2 = ToArrayNoDistribute<string | number>;
// 结果:(string | number)[]

never 的行为

// never 不触发分发,直接返回 never
type NeverTest = ToArray<never>; // never

// 原因:never 是空联合类型,没有成员可以分发
// 对空集的映射结果仍然是空集

详细用法

类型过滤

// 从联合类型中过滤出符合条件的成员
type Filter<T, U> = T extends U ? T : never;

// 只保留字符串类型
type OnlyStrings = Filter<string | number | boolean, string>;
// string

// 只保留函数类型
type OnlyFunctions = Filter<string | number | (() => void) | object, Function>;
// () => void

// 排除 null 和 undefined
type NonNull<T> = Filter<T, null | undefined>;
// 等价于 NonNullable<T>

类型映射

// 将联合类型中的某些成员替换为其他类型
type MapType<T, U, V> = T extends U ? V : T;

// 将 number 替换为 null
type ReplaceNumber = MapType<string | number | boolean, number, null>;
// string | null | boolean

// 将所有函数替换为字符串描述
type DescribeFunctions<T> = T extends (...args: any[]) => any
  ? `函数: ${T extends (...args: any[]) => infer R ? string : never}`
  : T;

控制分发

// 阻止分发:使用元组包裹
type NoDistribute<T> = [T] extends [never] ? true : false;

type A = NoDistribute<string | number>; // false(联合类型不是 never)
type B = NoDistribute<never>; // true(never 是 never)

// 只对非 never 分发
type Wrap<T> = [T] extends [never] ? never : T extends any ? { value: T } : never;

type C = Wrap<string | number>;
// { value: string } | { value: number }

type D = Wrap<never>; // never

检测联合类型

// 判断一个类型是否为联合类型
type IsUnion<T> = [T] extends [never]
  ? false
  : T extends any
    ? [T] extends [T]
      ? false
      : true
    : never;

// 原理解析:
// 1. 先排除 never
// 2. 利用分发的特性:如果 T 是联合类型,分发后 [T] extends [T] 会变成
//    [A] extends [A | B] => false
// 3. 如果 T 不是联合类型,[T] extends [T] => true

type Test1 = IsUnion<string>; // false
type Test2 = IsUnion<string | number>; // true
type Test3 = IsUnion<never>; // false

常见场景

API 响应类型处理

// 根据成功/失败分发不同的响应类型
type ApiResponse<T, E = string> = { success: true; data: T } | { success: false; error: E };

// 提取成功时的数据类型
type ExtractData<T> = T extends { success: true; data: infer D } ? D : never;

type UserData = ExtractData<ApiResponse<{ id: number; name: string }>>;
// { id: number; name: string }

// 提取失败时的错误类型
type ExtractError<T> = T extends { success: false; error: infer E } ? E : never;

type UserError = ExtractError<ApiResponse<{ id: number }, { code: number; message: string }>>;
// { code: number; message: string }

递归展开

// 递归展开嵌套数组
type Flatten<T> = T extends Array<infer U> ? Flatten<U> : T;

type Nested = string[][][];
type Flat = Flatten<Nested>; // string

// 展开对象中的 Promise
type UnwrapPromise<T> = T extends Promise<infer U> ? UnwrapPromise<U> : T;

type DeepPromise = Promise<Promise<Promise<number>>>;
type Unwrapped = UnwrapPromise<DeepPromise>; // number

条件类型与映射类型结合

// 只为可选属性生成 undefined 检查
type OptionalKeys<T> = {
  [K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];

// 为可选属性添加显式 undefined
type ExplicitUndefined<T> = {
  [K in keyof T]: K extends OptionalKeys<T> ? T[K] | undefined : T[K];
};

interface Config {
  name: string;
  timeout?: number;
  retries?: number;
}

type ExplicitConfig = ExplicitUndefined<Config>;
// { name: string; timeout: number | undefined; retries: number | undefined }

注意事项

  • 类型参数:只有裸类型参数直接出现在 extends 左侧的 T)才会触发分发。被 [T]{ value: T }裹的 T 不会分发
  • never 的特殊:never 作为联合类型时不会触发分发,因为它是空集。这可能导致条件类型的意外行为,需要用 [T] extends [never] 处理
  • 性能影响分发行为会增加类型检查的复杂于大型联合类型(超过 10 个成员),分发可能导致编译变慢。
  • 映射类型交互映射类型中的条件类型也会触发分发。如果不需要分发,在映射类型内部也应使用元组包裹。

进阶用法

类型级编程

// 使用条件类型分发实现类型级布尔运算
type And<A extends boolean, B extends boolean> = A extends true
  ? B extends true
    ? true
    : false
  : false;

type Or<A extends boolean, B extends boolean> = A extends true
  ? true
  : B extends true
    ? true
    : false;

type Not<A extends boolean> = A extends true ? false : true;

type TestAnd = And<true, true>; // true
type TestOr = Or<false, true>; // true
type TestNot = Not<true>; // false

联合类型排列组合

// 生成两个联合类型的排列组合
type Combine<A, B> = A extends any ? (B extends any ? [A, B] : never) : never;

type Pair = Combine<'a' | 'b', 1 | 2>;
// ['a', 1] | ['a', 2] | ['b', 1] | ['b', 2]

// 生成对象键值对
type ObjectFromEntries<T extends [string, any]> = {
  [K in T[0]]: Extract<T, [K, any]>[1];
};

type Entries = ['name', string] | ['age', number] | ['active', boolean];
type Obj = ObjectFromEntries<Entries>;
// { name: string; age: number; active: boolean }

条件类型链

// 多层条件类型链,类似模式匹配
type Match<T> = T extends string
  ? { type: 'string'; value: T }
  : T extends number
    ? { type: 'number'; value: T }
    : T extends boolean
      ? { type: 'boolean'; value: T }
      : T extends Array<any>
        ? { type: 'array'; value: T }
        : T extends object
          ? { type: 'object'; value: T }
          : { type: 'unknown'; value: T };

type StringMatch = Match<'hello'>; // { type: 'string'; value: 'hello' }
type NumberMatch = Match<42>; // { type: 'number'; value: 42 }
type ArrayMatch = Match<string[]>; // { type: 'array'; value: string[] }

知识检测

学习进度

-- 已学文档
--% 知识覆盖率

学习推荐

专注模式