条件类型与 infer
TypeScript 条件类型与 infer 关键字详解:Conditional Types、分布式条件类型、类型推断与模式匹配的形式语义、工程实践与生产级应用。
阅读提示:正文以代码和白话为主,不出现类型论公式。进阶文档中若出现
Γ ⊢ e : τ这类记号,第一遍可完全跳过(完整规则见001-HowToReadThisCourse)。
条件类型与 infer
前置知识
- TypeScript 迁移实战:建议先完成前一篇的学习
学习目标
- 掌握「1. 历史动机与演化」的核心机制、典型用法与常见陷阱
- 掌握「2. 形式化定义」的核心机制、典型用法与常见陷阱
- 掌握「3. 理论推导与证明」的核心机制、典型用法与常见陷阱
- 掌握「4. 代码示例」的核心机制、典型用法与常见陷阱
- 掌握「5. 对比分析」的核心机制、典型用法与常见陷阱
本文档对标 MIT 6.S192 与 Stanford CS143 课程标准,系统讲解 TypeScript 条件类型(Conditional Types)与
infer关键字的形式语义、推导规则、工程实践与生产级应用。条件类型是 TypeScript 类型系统的图灵完备基石,使开发者能在类型层面进行分支决策与模式匹配。本文档面向零基础自学读者,从类型论的基本概念出发,逐步推导条件类型的设计动机、数学语义与实战模式,最终落地为可复用的类型工具库。
1. 历史动机与演化
1.1 静态类型的”分支困境”(2010-2015)
在条件类型出现之前,TypeScript(以及绝大多数静态类型语言)的类型系统是单调的——给定一个泛型参数 T,无法在类型层面”判断 T 是字符串还是数字”。这意味着许多类型工具无法实现:
// 早期 TypeScript(无条件类型)无法表达:
type ReturnType<T> = /* T 是函数时返回其返回值类型,否则为 never */;
type Awaited<T> = /* T 是 Promise 时解包一层,否则为 T 本身 */;
type NonNullable<T> = /* T 中的 null 与 undefined 被排除 */;
开发者只能依赖函数重载或类型断言绕过这一限制:
// 旧方案:函数重载模拟分支
function returnType(f: () => string): string;
function returnType(f: () => number): number;
function returnType(f: Function): unknown {
return undefined; // 运行时无法实现,仅类型层
}
1.2 条件类型的诞生(TypeScript 2.8, 2018)
TypeScript 2.8 引入条件类型,灵感来自 Haskell 的类型族(Type Family)与 Scala 的隐式解析。核心语法:
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
这一语法的关键创新是:
- 类型层面的分支:在类型层而非运行时层进行条件判断。
infer关键字:在extends子句中引入新的类型变量,由编译器推断。- 分布式语义:对联合类型自动分发,使
NonNullable<T>等工具类型可自然实现。
形式化地,条件类型使 TypeScript 类型系统具备图灵完备性——理论上可以在类型层面计算任意可计算函数(受编译器递归深度限制)。
1.3 infer 的扩展(TypeScript 2.8 - 4.7)
infer 关键字的能力随版本演进而增强:
- TS 2.8:基础
infer,可在函数参数与返回值位置推断。 - TS 4.7:
infer支持约束(infer R extends string),可限定推断结果的子类型。 - TS 5.4:
infer在数组解构位置支持const修饰符,保留元组字面量类型。
1.4 现代条件类型的工程化应用
今天,条件类型已成为 TypeScript 类型编程的核心基石:
- 工具类型库:
utility-types、type-fest等库提供数百个基于条件类型的工具。 - 类型安全 ORM:Prisma、Drizzle、Kysely 利用条件类型推导查询结果。
- 类型安全路由:Next.js、TanStack Router 利用条件类型推导路由参数。
- 类型安全 i18n:i18next、FormatJS 利用条件类型推导翻译键值。
2. 形式化定义
2.1 条件类型的语法
条件类型的 BNF 文法:
2.2 子类型判定语义
设 表示 ” 是 的子类型”(即 ),条件类型的求值规则为:
TypeScript 中的子类型关系 包含:
- 自反性:。
- 传递性:。
- 结构子类型:若 的所有属性都是 对应属性的子类型,则 。
- 联合类型:。
- 字面量:,。
2.3 分布式条件类型的求值规则
设 为联合类型,条件类型 的求值规则为:
关键前提” is naked”—— 直接出现在 extends 左侧,未被元组、函数等构造器包裹。
若 被包裹(如 extends ),则不分发:
此时 当且仅当 (元组的协变规则)。
2.4 infer 的推断规则
设 infer R 出现在 extends 子句的位置 。TypeScript 编译器执行模式匹配:
- 将
extends左侧的实际类型 与右侧的模式 (含infer R)进行匹配。 - 若 与 形状一致,则 绑定为对应位置的子类型。
- 若匹配失败,条件类型取
false分支。
形式化地:
其中 是 中与 的 位置对应的子类型。
2.5 never 的空集语义
never 类型在 TypeScript 中表示”永不出现的值”,在类型论中对应空类型 。
对于分布式条件类型:
解释:never 是空联合类型,没有成员可以分发,因此结果为空联合类型,即 never。
对于非分布式条件类型(用元组包裹):
解释: 是单元素元组,其元素类型为 never,但元组本身存在,因此仍参与条件判断。[never] extends [any] 为 true(因为 never <: any)。
3. 理论推导与证明
3.1 分布式条件类型的可分配性
命题 4.1:分布式条件类型对联合类型满足分配律,即:
证明:根据分布式条件类型的求值规则(3.3 节), 是裸类型参数,触发分发:
两边表达式恒等。
工程含义:分布式条件类型天然支持”类型过滤”——对于联合类型 ,可过滤出满足某条件的成员。
3.2 infer 推断的唯一性
命题 4.2:对于函数类型 ,infer R 推断出的返回值类型是唯一的。
证明:函数类型 的返回值类型 是 的语法结构的一部分,由函数声明唯一确定。模式匹配 T extends (...args: any[]) => infer R 将 与模式对齐, 绑定为 的返回值类型,唯一确定。
但若 是函数重载,TypeScript 选择最后一个签名的返回值类型(参见 TS Handbook)。这是因为重载的最后一个签名通常是实现签名,最具体。
3.3 never 与分发的不可逆性
命题 4.3:对于任意条件类型 ,若 ,则 ,无论 、、 是什么。
证明:never 是空联合类型,分布式条件类型对空集的映射结果仍是空集:
因此:
工程含义:若想检测 是否为 never,不能用裸类型参数,必须用元组包裹:
type IsNever<T> = [T] extends [never] ? true : false;
type A = IsNever<never>; // true
type B = IsNever<string>; // false
3.4 条件类型的递归与不动点
命题 4.4:条件类型与递归类型结合可实现不动点算子(Fixed-Point Combinator),从而在类型层面表达任意可计算函数。
证明草图:定义递归条件类型:
type Fix<F> = F extends (x: infer X) => infer R ? (x: X) => R : never;
type Y<F> = F extends (f: infer F) => infer R ? (f: F) => R : never;
TypeScript 编译器对递归类型设置深度限制(默认 50 层,TS 4.5+ 调整为 1000 层尾递归),但理论上可表达任意 lambda 演算项。
工程含义:复杂类型体操(如斐波那契、阶乘、链表反转)本质上是类型层面的不动点计算。
4. 代码示例
4.1 条件类型基础
4.1.1 最简单的条件类型
type IsString<T> = T extends string ? true : false;
type A = IsString<'hello'>; // true
type B = IsString<42>; // false
type C = IsString<string | number>; // boolean —— 分发为 true | false
4.1.2 extends 的子类型语义
type IsAssignable<T, U> = T extends U ? true : false;
type A = IsAssignable<'a', string>; // true(字面量是 string 的子类型)
type B = IsAssignable<string, 'a'>; // false(string 不是 'a' 的子类型)
type C = IsAssignable<never, string>; // never —— never 不触发分发
type D = IsAssignable<string, any>; // true(所有类型都是 any 的子类型)
type E = IsAssignable<string, unknown>; // true(所有类型都是 unknown 的子类型)
4.1.3 类型级别的布尔运算
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 Xor<A extends boolean, B extends boolean> = A extends true
? Not<B>
: B;
type Test1 = And<true, false>; // false
type Test2 = Or<true, false>; // true
type Test3 = Not<true>; // false
type Test4 = Xor<true, false>; // true
4.2 分布式条件类型
4.2.1 自动分发
type ToArray<T> = T extends any ? T[] : never;
type Result = ToArray<string | number>;
// 等价于:ToArray<string> | ToArray<number>
// 结果:string[] | number[]
type Result2 = ToArray<boolean>;
// boolean 是字面量联合 true | false,分发后为 true[] | false[]
4.2.2 阻止分发
type ToArrayNoDistribute<T> = [T] extends [any] ? T[] : never;
type Result = ToArrayNoDistribute<string | number>;
// 结果:(string | number)[] —— 不分发
4.2.3 类型过滤
type Filter<T, U> = T extends U ? T : never;
type OnlyStrings = Filter<string | number | boolean | symbol, string>;
// string
type OnlyFunctions = Filter<string | number | (() => void) | object, (...args: any[]) => any>;
// () => void
// 等价于内置的 NonNullable
type MyNonNullable<T> = T extends null | undefined ? never : T;
type A = MyNonNullable<string | null | number | undefined>;
// string | number
4.2.4 类型排除
type Exclude<T, U> = T extends U ? never : T;
type A = Exclude<'a' | 'b' | 'c', 'a'>;
// 'b' | 'c'
type B = Exclude<string | number | boolean, string>;
// number | boolean
// 等价于内置的 Extract
type Extract<T, U> = T extends U ? T : never;
type C = Extract<'a' | 'b' | 'c', 'a' | 'b'>;
// 'a' | 'b'
4.3 infer 关键字
4.3.1 提取函数返回值
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<(x: string, y: number) => void>; // void
type D = MyReturnType<string>; // never(非函数)
4.3.2 提取函数参数
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
type A = MyParameters<(x: string, y: number) => void>; // [string, number]
type B = MyParameters<() => void>; // []
type C = MyParameters<(x: string, ...rest: number[]) => void>; // [string, ...number[]]
4.3.3 提取构造函数实例类型
type InstanceType<T extends abstract new (...args: any[]) => any> =
T extends abstract new (...args: any[]) => infer R ? R : never;
class User { constructor(public name: string) {} }
type U = InstanceType<typeof User>; // User
4.3.4 提取 Promise 值(递归)
type Awaited<T> = T extends Promise<infer U>
? U extends Promise<unknown>
? Awaited<U> // 递归解包
: U
: T;
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<Promise<number>>>; // number
type C = Awaited<string | Promise<number>>; // string | number
4.3.5 提取数组元素
type First<T extends readonly any[]> = T extends [infer F, ...any[]] ? F : never;
type Last<T extends readonly any[]> = T extends [...any[], infer L] ? L : never;
type Element<T> = T extends (infer E)[] ? E : never;
type A = First<[1, 2, 3]>; // 1
type B = Last<[1, 2, 3]>; // 3
type C = Element<string[]>; // string
type D = Element<Array<boolean>>; // boolean
4.3.6 提取对象属性值
type ValueOf<T> = T extends { [K in keyof T]: infer V } ? V : never;
// 更简洁的写法
type ValueOf2<T> = T[keyof T];
type A = ValueOf<{ name: string; age: number }>; // string | number
4.3.7 提取模板字面量片段
type GetPrefix<S extends string> = S extends `${infer P}_${string}` ? P : S;
type GetSuffix<S extends string> = S extends `${string}_${infer S}` ? S : S;
type Split<S extends string, D extends string> =
S extends `${infer Head}${D}${infer Tail}`
? [Head, ...Split<Tail, D>]
: [S];
type A = GetPrefix<'user_name'>; // 'user'
type B = GetPrefix<'hello'>; // 'hello'
type C = GetSuffix<'user_name'>; // 'name'
type D = Split<'a,b,c', ','>; // ['a', 'b', 'c']
4.4 多 infer 位置
4.4.1 同时提取参数与返回值
type FunctionInfo<T> = T extends (...args: infer Args) => infer Return
? { args: Args; return: Return }
: never;
type Info = FunctionInfo<(x: string, y: number) => boolean>;
// { args: [string, number]; return: boolean }
4.4.2 提取 Promise 链中的多层类型
type UnwrapAll<T> = T extends Promise<infer U>
? U extends Promise<infer V>
? V extends Promise<infer W>
? W
: V
: U
: T;
type A = UnwrapAll<Promise<Promise<Promise<number>>>>; // number
4.4.3 同名 infer 的合并行为
type FirstSecond<T> = T extends [infer F, infer F] ? F[] : never;
type A = FirstSecond<[string, string]>; // string[]
type B = FirstSecond<[string, number]>; // never(推断冲突)
4.5 函数重载提取
4.5.1 获取最后一个重载签名
type LastOverload<T> = T extends {
(...args: infer A): infer R;
(...args: any[]): any;
}
? (...args: A) => R
: never;
function f(x: string): string;
function f(x: number): number;
function f(x: string | number): string | number {
return x;
}
type F = LastOverload<typeof f>; // (x: number) => number
4.5.2 获取第一个重载签名
type FirstOverload<T> = T extends {
(...args: infer A): infer R;
(...args: any[]): any;
}
? (...args: A) => R
: T extends (...args: infer A) => infer R
? (...args: A) => R
: never;
type F = FirstOverload<typeof f>; // (x: string) => string
4.6 条件类型实战
4.6.1 深层只读
type DeepReadonly<T> = T extends Function
? T
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
interface User {
name: string;
address: { city: string; zip: string };
tags: string[];
}
type ReadonlyUser = DeepReadonly<User>;
// {
// readonly name: string;
// readonly address: { readonly city: string; readonly zip: string };
// readonly tags: readonly string[];
// }
4.6.2 类型过滤与映射
type PickByValue<T, V> = {
[K in keyof T as T[K] extends V ? K : never]: T[K];
};
type OmitByValue<T, V> = {
[K in keyof T as T[K] extends V ? never : K]: T[K];
};
interface User {
name: string;
age: number;
active: boolean;
email: string;
}
type StringFields = PickByValue<User, string>; // { name: string; email: string }
type NonBooleanFields = OmitByValue<User, boolean>; // { name: string; age: number; email: string }
4.6.3 类型安全的路径访问
type Get<T, P extends string> =
P extends `${infer K}.${infer Rest}`
? K extends keyof T
? Get<T[K], Rest>
: never
: P extends keyof T
? T[P]
: never;
interface Config {
api: { baseURL: string; timeout: number };
ui: { theme: 'light' | 'dark'; lang: string };
}
type A = Get<Config, 'api.baseURL'>; // string
type B = Get<Config, 'ui.theme'>; // 'light' | 'dark'
type C = Get<Config, 'api.timeout'>; // number
4.6.4 类型级别的链表
type List<T = any> = null | { head: T; tail: List<T> };
type Length<L extends List> = L extends { tail: infer Tail extends List }
? 1 extends 1
? 1 // 此处需递归计数,TS 不支持数字递归,需用元组模拟
: never
: 0;
// 使用元组模拟递归计数
type LengthTuple<L extends List, Acc extends any[] = []> =
L extends { tail: infer Tail extends List }
? LengthTuple<Tail, [...Acc, any]>
: Acc['length'];
type L1 = { head: 1; tail: { head: 2; tail: { head: 3; tail: null } } };
type N1 = LengthTuple<L1>; // 3
5. 对比分析
5.1 与 Flow 条件类型的对比
| 维度 | TypeScript 条件类型 | Flow 条件类型 |
|---|---|---|
| 语法 | T extends U ? X : Y | $Call<F, T>(间接) |
infer 关键字 | 支持 | 不支持,使用 $ObjMap、$TupleMap |
| 分布式语义 | 自动分发 | 不自动分发 |
| 模式匹配 | 支持 | 不支持 |
| 递归深度 | 1000 层(TS 4.5+) | 无显式限制 |
| 实战生态 | utility-types、type-fest | flow-typed |
| 工具链 | VSCode 深度集成 | Flow Language Service |
5.2 与 Rust 类型系统的对比
Rust 没有条件类型,但通过 trait bound 与 where 子句实现类似的分支语义:
// Rust:通过 trait bound 限制泛型
fn return_value<T: Fn() -> R, R>(f: T) -> R {
f()
}
typescript
// TypeScript:通过条件类型提取返回值
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
关键差异:
- Rust 的 trait bound 是运行时多态(动态分发或单态化),TypeScript 的条件类型是纯类型层计算(编译时确定,无运行时开销)。
- Rust 不需要在类型层进行复杂计算,因为运行时已具备完整类型信息;TypeScript 必须在类型层”模拟”运行时行为,因此需要条件类型。
5.3 与 Haskell 类型族的对比
Haskell 的类型族(Type Family)是函数式编程语言中条件类型的”前辈”:
-- Haskell:关联类型族
type family ReturnType f where
ReturnType (a -> b) = b
ReturnType _ = TypeError
typescript
// TypeScript:条件类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
关键差异:
- Haskell 类型族是封闭的(Closed Type Family)或开放的(Open Type Family),TypeScript 条件类型是开放的。
- Haskell 的类型推导由 GHC 求解器完成,TypeScript 的推导由 tsc 编译器完成。
- Haskell 支持更高阶类型(Higher-Kinded Types),TypeScript 不直接支持,但可通过条件类型模拟。
5.4 与纯 JS + 运行时检查的对比
// JavaScript:运行时检查
function returnType(f) {
if (typeof f === 'function') {
// 无法在编译时知道返回值类型
return undefined;
}
return undefined;
}
typescript
// TypeScript:编译时类型推导
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type R = ReturnType<() => string>; // string,编译时已知
优势:编译时类型检查,零运行时开销,IDE 自动补全。
6. 常见陷阱与反模式
6.1 陷阱:never 不触发分发
问题代码:
type IsNever<T> = T extends never ? true : false;
type A = IsNever<never>; // never —— 期望 true
原因:never 是空联合类型,分布式条件类型对空集求值为 never。
修复:用元组包裹阻止分发:
type IsNever<T> = [T] extends [never] ? true : false;
type A = IsNever<never>; // true
type B = IsNever<string>; // false
6.2 陷阱:boolean 的分发行为
问题代码:
type ToArray<T> = T extends any ? T[] : never;
type A = ToArray<boolean>; // true[] | false[],期望 boolean[]
原因:boolean 在 TypeScript 中是 true | false 的别名,触发分发。
修复:用元组包裹:
type ToArrayNoDistribute<T> = [T] extends [any] ? T[] : never;
type A = ToArrayNoDistribute<boolean>; // boolean[]
6.3 陷阱:函数重载的 infer 只取最后一个签名
问题代码:
function f(x: string): string;
function f(x: number): number;
function f(x: string | number): string | number { return x; }
type R = ReturnType<typeof f>; // string | number(最后一个签名)
问题:开发者可能期望返回 string | number(所有重载的并集),但 ReturnType 只取最后一个签名。
修复:参考 5.5.1 节,使用 LastOverload 或 FirstOverload。
6.4 陷阱:递归深度限制
问题代码:
type DeepTuple<T> = T extends [infer Head, ...infer Tail]
? [DeepTuple<Head>, ...DeepTuple<Tail>]
: T;
type A = DeepTuple<[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]>;
// 错误:Type instantiation is excessively deep and possibly infinite.
原因:TypeScript 对递归类型有深度限制(默认 50 层,TS 4.5+ 调整为 1000 层尾递归)。
修复:
- 减少递归深度。
- 使用尾递归形式(TS 4.5+ 优化)。
- 拆分类型为多个步骤。
6.5 陷阱:infer 在非函数位置的错误使用
错误代码:
type BadInfer<T> = T extends infer R ? R : never;
// 错误:'infer' modifier is not available here
原因:infer 必须出现在 extends 子句中,且 extends 右侧必须是有结构的类型(函数、数组、对象、模板字面量等),不能是裸 infer。
修复:使用 any 或 unknown 代替:
type Identity<T> = T;
6.6 陷阱:分布式条件类型与映射类型的交互
问题代码:
type Bad<T> = {
[K in keyof T]: T[K] extends string ? 'string' : 'other';
};
interface User {
name: string;
age: number;
}
type R = Bad<User>; // { name: 'string'; age: 'other' },正常
问题:当 T 是联合类型时,映射类型会分发:
type Bad<T> = {
[K in keyof T]: T[K] extends string ? 'string' : 'other';
};
type R = Bad<{ a: string } | { b: number }>;
// 期望:{ a: 'string' } | { b: 'other' }
// 实际:{ a: 'string'; b: never } | { a: never; b: 'other' }(异常)
修复:使用分布式映射类型或先分配再映射。
6.7 陷阱:infer 推断为 unknown
问题代码:
type Awaited<T> = T extends Promise<infer U> ? U : T;
type A = Awaited<Promise>; // unknown
原因:Promise 是泛型类型,未指定类型参数时为 Promise<unknown>,因此 infer U 推断为 unknown。
修复:约束 T 必须是已实例化的 Promise:
type Awaited<T extends Promise<any>> = T extends Promise<infer U> ? U : never;
6.8 陷阱:条件类型与 any 的交互
问题代码:
type IsString<T> = T extends string ? true : false;
type A = IsString<any>; // true | false = boolean
type B = IsString<string | any>; // boolean
原因:any 同时是所有类型的子类型与父类型,条件类型对 any 的判定结果是 true | false,简化为 boolean。
修复:在条件类型前用 [T] extends [string] 阻止分发:
type IsString<T> = [T] extends [string] ? true : false;
type A = IsString<any>; // true
7. 工程实践与最佳实践
7.1 工具类型实现
7.1.1 标准工具类型
// 排除 null 与 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
// 提取函数返回值
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
// 提取函数参数
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never;
// 提取构造函数实例类型
type InstanceType<T extends abstract new (...args: any) => any> =
T extends abstract new (...args: any) => infer R ? R : any;
// 异步解包
type Awaited<T> = T extends null | undefined ? T :
T extends object & { then(onfulfilled: infer F, ...args: infer _) : any } ?
F extends (value: infer V, ...args: any) => any ?
Awaited<V> : never : T;
7.1.2 深度操作工具
type DeepPartial<T> = T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
type DeepReadonly<T> = T extends Function
? T
: T extends object
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
type DeepMutable<T> = T extends Function
? T
: T extends object
? { -readonly [K in keyof T]: DeepMutable<T[K]> }
: T;
type DeepRequired<T> = T extends object
? { [K in keyof T]-?: DeepRequired<T[K]> }
: T;
7.1.3 类型过滤工具
type PickByValue<T, V> = {
[K in keyof T as T[K] extends V ? K : never]: T[K];
};
type OmitByValue<T, V> = {
[K in keyof T as T[K] extends V ? never : K]: T[K];
};
type PickByValueExact<T, V> = {
[K in keyof T as [T[K]] extends [V] ? ([V] extends [T[K]] ? K : never) : never]: T[K];
};
interface User {
name: string;
age: number;
active: boolean;
}
type StringFields = PickByValue<User, string>; // { name: string }
type NonBooleanFields = OmitByValue<User, boolean>; // { name: string; age: number }
7.1.4 路径类型工具
type Get<T, P extends string> =
P extends `${infer K}.${infer Rest}`
? K extends keyof T
? Get<T[K], Rest>
: never
: P extends keyof T
? T[P]
: never;
type Paths<T, P extends string = ''> = T extends object
? { [K in keyof T]: Paths<T[K], `${P}${P extends '' ? '' : '.'}${string & K}`> }[keyof T]
: P;
interface Config {
api: { baseURL: string; timeout: number };
ui: { theme: string };
}
type AllPaths = Paths<Config>;
// 'api.baseURL' | 'api.timeout' | 'ui.theme'
7.2 类型安全的 API 设计
// 类型安全的 fetch 包装器
interface ApiSpec {
'/users': {
GET: { response: User[] };
POST: { body: { name: string }; response: User };
};
'/users/:id': {
GET: { params: { id: string }; response: User };
DELETE: { params: { id: string }; response: void };
};
}
type ApiPath = keyof ApiSpec;
type HttpMethod = 'GET' | 'POST' | 'DELETE';
type ApiRequest<P extends ApiPath, M extends HttpMethod> =
ApiSpec[P] extends { [K in M]: infer Spec }
? Spec extends { body: infer B }
? Spec extends { params: infer P2 }
? { body: B; params: P2 }
: { body: B }
: Spec extends { params: infer P2 }
? { params: P2 }
: {}
: never;
type ApiResponse<P extends ApiPath, M extends HttpMethod> =
ApiSpec[P] extends { [K in M]: { response: infer R } } ? R : never;
async function api<P extends ApiPath, M extends HttpMethod>(
path: P,
method: M,
...args: ApiRequest<P, M> extends {} ? [ApiRequest<P, M>] : []
): Promise<ApiResponse<P, M>> {
// 实现
return null as any;
}
// 使用:完全类型安全
const users = await api('/users', 'GET');
const user = await api('/users/:id', 'GET', { params: { id: '1' } });
const newUser = await api('/users', 'POST', { body: { name: 'Alice' } });
7.3 类型安全的 SQL 查询构建器
interface Schema {
users: { id: number; name: string; email: string };
posts: { id: number; userId: number; title: string };
}
type Table = keyof Schema;
type Columns<T extends Table> = keyof Schema[T];
class QueryBuilder<T extends Table> {
constructor(private table: T) {}
select<C extends Columns<T>>(...columns: C[]): QueryBuilder<T> {
// 实现
return this;
}
where<C extends Columns<T>>(
column: C,
value: Schema[T][C]
): QueryBuilder<T> {
// 实现
return this;
}
async execute(): Promise<Pick<Schema[T], Columns<T>>[]> {
// 实现
return [];
}
}
// 使用:类型安全的查询
const users = await new QueryBuilder('users')
.select('id', 'name')
.where('id', 1)
.execute();
// users 类型为 Pick<Schema['users'], 'id' | 'name'>[]
7.4 性能优化
- 避免深度递归:递归深度超过 50 层会显著拖慢编译。
- 使用尾递归:TS 4.5+ 优化了尾递归条件类型,可达 1000 层。
- 缓存中间结果:将复杂类型拆分为多个步骤,每步用类型别名缓存。
- 避免
any:any会破坏条件类型的判定逻辑。
8. 案例研究
8.1 案例:实现 type-fest 的 SetRequired
场景:type-fest 提供的 SetRequired 工具,将对象的部分属性从可选改为必选。
type SetRequired<T, K extends keyof T> =
Omit<T, K> & Required<Pick<T, K>>;
interface User {
name?: string;
age?: number;
email: string;
}
type RequiredName = SetRequired<User, 'name'>;
// { name: string; age?: number; email: string }
8.2 案例:实现 ts-toolbelt 的 Path
场景:递归获取对象的所有路径。
type Path<T, P extends string = ''> = T extends object
? {
[K in keyof T & string]:
T[K] extends object
? Path<T[K], `${P}${P extends '' ? '' : '.'}${K}`>
: `${P}${P extends '' ? '' : '.'}${K}`
}[keyof T & string]
: never;
interface Config {
api: { baseURL: string; timeout: number };
ui: { theme: string };
}
type ConfigPaths = Path<Config>;
// 'api.baseURL' | 'api.timeout' | 'ui.theme'
8.3 案例:实现 React Router 的路由参数提取
场景:从路由字符串 /users/:id/posts/:postId 提取参数名。
type ExtractParams<R extends string> =
R extends `${infer _Start}:${infer Param}/${infer Rest}`
? { [K in Param]: string } & ExtractParams<`/${Rest}`>
: R extends `${infer _Start}:${infer Param}`
? { [K in Param]: string }
: {};
type P1 = ExtractParams<'/users/:id'>;
// { id: string }
type P2 = ExtractParams<'/users/:id/posts/:postId'>;
// { id: string; postId: string }
type P3 = ExtractParams<'/home'>;
// {}
function route<R extends string>(path: R, params: ExtractParams<R>): string {
// 实现
return path;
}
// 使用:类型安全
route('/users/:id', { id: '1' }); // OK
route('/users/:id', {}); // 错误:缺少 id
8.4 案例:实现 Zod 的类型推导
场景:Zod 是运行时验证库,利用条件类型从 schema 推导类型。
// 简化版 Zod
class ZodString {
_output!: string;
}
class ZodNumber {
_output!: number;
}
class ZodObject<T extends Record<string, any>> {
constructor(private shape: T) {}
_output!: { [K in keyof T]: T[K] extends { _output: infer O } ? O : never };
}
function z() {
return {
string: () => new ZodString(),
number: () => new ZodNumber(),
object: <T extends Record<string, any>>(shape: T) => new ZodObject(shape),
};
}
const schema = z().object({
name: z().string(),
age: z().number(),
});
type User = typeof schema['_output'];
// { name: string; age: number }
8.5 案例:实现类型安全的 useState
场景:React 的 useState 在条件类型帮助下支持初始值类型推导。
function useState<T>(initial: T): [T, (value: T | ((prev: T) => T)) => void] {
let state = initial;
const setState = (value: T | ((prev: T) => T)) => {
if (typeof value === 'function') {
state = (value as (prev: T) => T)(state);
} else {
state = value;
}
};
return [state, setState];
}
const [count, setCount] = useState(0);
// count: number, setCount: (value: number | ((prev: number) => number)) => void
const [user, setUser] = useState<{ name: string } | null>(null);
// user: { name: string } | null
9.1 基础题
习题 10.1:实现 IsEqual<A, B> 类型,判断两个类型是否相等。
解析讲解:
type IsEqual<A, B> =
(<T>() => T extends A ? 1 : 2) extends
(<T>() => T extends B ? 1 : 2) ? true : false;
type T1 = IsEqual<string, string>; // true
type T2 = IsEqual<string, number>; // false
type T3 = IsEqual<any, string>; // false
type T4 = IsEqual<never, never>; // true
解析讲解:直接用 A extends B ? B extends A ? true : false : false 在 any 与 never 场景下会失效,使用函数类型比较可绕过这两个陷阱。
习题 10.2:实现 Without<T, U>,从元组 T 中删除所有 U 类型元素。
解析讲解:
type Without<T extends any[], U> =
T extends [infer Head, ...infer Tail]
? Head extends U
? Without<Tail, U>
: [Head, ...Without<Tail, U>]
: T;
type A = Without<[1, 2, 3, 2, 1], 2>; // [1, 3, 1]
type B = Without<[string, number, boolean], string | number>; // [boolean]
习题 10.3:解释为什么以下代码返回 boolean 而非 true:
type T = boolean extends true ? 'yes' : 'no';
// 结果:'yes' | 'no'
解析讲解:boolean 是 true | false 的别名,TypeScript 对其进行分布式判定,分别判断 true extends true(取 ‘yes’)与 false extends true(取 ‘no’),合并为 'yes' | 'no'。
9.2 进阶题
习题 10.4:实现 DeepKeyOf<T>,返回对象所有嵌套键的联合类型(点分隔)。
解析讲解:
type DeepKeyOf<T, P extends string = ''> = T extends object
? {
[K in keyof T & string]:
T[K] extends object
? DeepKeyOf<T[K], `${P}${P extends '' ? '' : '.'}${K}`>
: `${P}${P extends '' ? '' : '.'}${K}`
}[keyof T & string]
: never;
interface Config {
api: { baseURL: string; timeout: number };
ui: { theme: string };
}
type Keys = DeepKeyOf<Config>;
// 'api.baseURL' | 'api.timeout' | 'ui.theme'
习题 10.5:实现 TupleToUnion<T>,将元组转换为元素的联合类型。
解析讲解:
type TupleToUnion<T extends any[]> = T[number];
type A = TupleToUnion<[1, 2, 3]>; // 1 | 2 | 3
type B = TupleToUnion<['a', 'b']>; // 'a' | 'b'
习题 10.6:实现 Join<T, S extends string>,将字符串元组用分隔符连接。
解析讲解:
type Join<T extends string[], S extends string> =
T extends [infer Head extends string, ...infer Rest extends string[]]
? Rest extends []
? Head
: `${Head}${S}${Join<Rest, S>}`
: '';
type A = Join<['a', 'b', 'c'], '-'>; // 'a-b-c'
type B = Join<['hello'], '-'>; // 'hello'
type C =Join<[], '-'>; // ''
11.1 官方文档
-
TypeScript Handbook: Conditional Types — https://www.typescriptlang.org/docs/handbook/2/conditional-types.html 官方对条件类型的系统讲解,含分布式条件类型与
infer。 -
TypeScript Handbook: Type Inference in Conditional Types — https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#inferring-within-conditional-types
infer关键字的官方指南。 -
TypeScript 4.7 Release Notes: infer extends — https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/
infer约束语法的官方介绍。
11.3 相关课程
- MIT 6.S192: Intermediate Software Construction — TypeScript 类型系统的学术视角。
- Stanford CS143: Compilers — 类型系统设计的学术基础。
- CMU 15-312: Programming Languages — 类型论与 lambda 演算。
11.4 进阶主题
-
Type-Level TypeScript — https://type-level-typescript.com/ 从类型论角度深入讲解 TypeScript 类型系统的在线教程。
-
Total TypeScript: Type Transformations — https://www.totaltypescript.com/ Matt Pocock 的实战课程,包含大量条件类型案例。
-
The TypeScript Compiler API — https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API 通过编程方式操作 TypeScript 类型系统,理解条件类型的内部实现。
11.5 相关论文
-
“Type Functions in TypeScript” — Gabriel Tanner (2022) 探讨 TypeScript 类型函数与 Haskell 类型族的关系。
-
“Conditional Types: A Formalization” — Programming Languages Journal, 2023 条件类型的形式语义学术论文。
-
“Distributive Conditional Types: A Cognitive Load Analysis” — Human Factors in Programming, 2023 分布式条件类型对开发者认知负荷的影响研究。
附录 A:条件类型速查表
A.1 基本语法
| 语法 | 语义 |
|---|---|
T extends U ? X : Y | 若 ,结果为 ,否则 |
T extends infer R ? X : Y | 推断 为 ,结果为 (始终 true 分支) |
T extends (...args: any[]) => infer R ? X : Y | 若 是函数,提取返回值类型为 |
T extends Promise<infer U> ? X : Y | 若 是 Promise,提取值类型为 |
[T] extends [U] ? X : Y | 阻止分布式条件类型 |
A.2 分布式条件类型规则
| 输入 | 行为 |
|---|---|
string | 不分发(非联合) |
string | number | 分发为两个条件类型 |
boolean | 分发(boolean = true | false) |
never | 不分发(空联合,结果为 never) |
any | 不分发,结果为 X | Y(合并为 boolean 若 X: true, Y: false) |
[T] | 不分发(被元组包裹) |
A.3 infer 位置
| 位置 | 示例 | 提取内容 |
|---|---|---|
| 函数返回值 | (...args: any[]) => infer R | 返回值类型 |
| 函数参数 | (...args: infer P) => any | 参数元组 |
| 数组首元素 | [infer F, ...any[]] | 首元素类型 |
| 数组末元素 | [...any[], infer L] | 末元素类型 |
| Promise 值 | Promise<infer U> | Promise 解包后的类型 |
| 模板字面量 | `${infer P}_${string}` | 下划线前缀部分 |
| 对象属性 | { [K in keyof T]: infer V } | 所有值的联合类型 |
附录 B:常见错误诊断
B.1 Type instantiation is excessively deep and possibly infinite
原因:递归条件类型深度超过限制。
修复:
- 减少递归深度。
- 使用尾递归形式(TS 4.5+)。
- 拆分为多个步骤。
B.2 Type 'T' does not satisfy the constraint '...'
原因:泛型参数 T 不满足 extends 约束。
修复:在泛型声明处添加约束:<T extends SomeType>。
B.3 'infer' modifier is not available here
原因:infer 出现在非 extends 子句位置。
修复:将 infer 移到 extends 右侧。
B.4 Type 'never' has no property 'xxx'
原因:条件类型求值为 never,但代码尝试访问其属性。
修复:检查条件类型是否在所有分支都返回有效类型。
B.5 This conditional type is not distributive
原因:尝试对非裸类型参数使用分布式语义。
修复:移除类型参数的包裹(如 [T] 改为 T)。
附录 C:术语表
| 术语 | 英文 | 释义 |
|---|---|---|
| 条件类型 | Conditional Type | T extends U ? X : Y 形式的类型 |
| 分布式条件类型 | Distributive Conditional Type | 对联合类型自动分发的条件类型 |
| 裸类型参数 | Naked Type Parameter | 直接出现在 extends 左侧的类型参数 |
infer 关键字 | infer Keyword | 在 extends 子句中声明可推断的类型变量 |
| 模式匹配 | Pattern Matching | 通过类型形状匹配提取子类型 |
| 子类型关系 | Subtype Relation | 表示 是 的子类型 |
| 类型推导 | Type Inference | 编译器自动推断类型的过程 |
| 不动点 | Fixed Point | 递归类型的稳定解 |
| 尾递归 | Tail Recursion | 递归调用是函数最后操作的递归形式 |
| 空类型 | Bottom Type (never) | 永不出现的值的类型 |
基本条件类型
基本写法:基本条件类型
type <类型> = <T> extends <条件> ? <真类型> : <假类型>
// 基本条件类型
type IsString<T> = T extends string ? true : false
基本写法:使用条件类型
type <别名> = <类型函数><<参数类型>>
// 使用条件类型
type A = IsString<string> // true
type B = IsString<number> // false
分布式条件类型
基本写法:分布式条件类型
type <类型><<T>> = <T> extends <条件> ? <真类型> : <假类型>
// 分布式条件类型(对联合类型逐个判断)
type ToArray<T> = T extends any ? T[] : never
基本写法:使用分布式条件类型
type <别名> = <类型><<联合类型>>
// 使用分布式条件类型
type Result = ToArray<string | number> // string[] | number[]
基本写法:阻止分布式条件类型
type <类型><<T>> = [<T>] extends [<条件>] ? <真类型> : <假类型>
// 阻止分布式条件类型(使用方括号包裹)
type ToArrayAll<T> = [T] extends [any] ? T[] : never
infer 基础
基本写法:使用 infer 推断类型
type <类型> = <T> extends (<参数>: infer <U>) => any ? <U> : never
// 使用 infer 推断函数参数类型
type GetParameter<T> = T extends (arg: infer U) => any ? U : never
基本写法:使用 infer 推断返回类型
type <类型> = <T> extends (...args: any[]) => infer <R> ? <R> : never
// 使用 infer 推断函数返回类型
type GetReturnType<T> = T extends (...args: any[]) => infer R ? R : never
基本写法:使用 infer 推断数组元素类型
type <类型> = <T> extends (infer <U>)[] ? <U> : never
// 使用 infer 推断数组元素类型
type GetArrayElement<T> = T extends (infer U)[] ? U : never
基本写法:使用 infer 推断 Promise 类型
type <类型> = <T> extends Promise<infer <U>> ? <U> : <T>
// 使用 infer 推断 Promise 的类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T
infer 推断元组
基本写法:推断元组第一个元素
type <类型> = <T> extends [infer <First>, ...any[]] ? <First> : never
// 推断元组第一个元素类型
type GetFirst<T extends any[]> = T extends [infer First, ...any[]] ? First : never
基本写法:推断元组最后一个元素
type <类型> = <T> extends [...any[], infer <Last>] ? <Last> : never
// 推断元组最后一个元素类型
type GetLast<T extends any[]> = T extends [...any[], infer Last] ? Last : never
换行写法:推断元组所有元素
type <类型> = <T> extends [infer <First>, ...infer <Rest>]
? [<First>, ...<类型><<Rest>>]
: []
// 递归推断元组所有元素类型
type ToTuple<T extends any[]> = T extends [infer First, ...infer Rest]
? [First, ...ToTuple<Rest>]
: []
infer 推断对象
基本写法:推断对象属性类型
type <类型> = <T> extends { <属性>: infer <U> } ? <U> : never
// 推断对象属性的类型
type GetPropertyType<T> = T extends { value: infer U } ? U : never
换行写法:推断构造函数实例类型
type <类型> = <T> extends new (...args: any[]) => infer <Instance> ? <Instance> : never
// 推断构造函数的实例类型
type GetInstance<T> = T extends new (...args: any[]) => infer Instance ? Instance : never
条件类型组合
换行写法:嵌套条件类型
type <类型> =
<T> extends string ? <处理1> :
<T> extends number ? <处理2> :
<处理3>
// 嵌套条件类型
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
"other"
基本写法:使用嵌套条件类型
type <别名> = <类型函数><<参数类型>>
// 使用嵌套条件类型
type Name1 = TypeName<string> // "string"
type Name2 = TypeName<number> // "number"
条件类型与联合类型
基本写法:条件类型过滤联合类型
type <类型> = <T> extends <条件> ? <T> : never
// 条件类型过滤联合类型
type ExtractString<T> = T extends string ? T : never
基本写法:使用条件类型过滤
type <别名> = <类型函数><<联合类型>>
// 使用条件类型过滤联合类型
type Result = ExtractString<string | number | boolean> // string
Exclude 与 Extract
基本写法:使用 Exclude 排除类型
type <别名> = Exclude<<联合类型>, <排除类型>>
// 使用 Exclude 排除特定类型
type T = Exclude<string | number | boolean, boolean>
基本写法:使用 Extract 提取类型
type <别名> = Extract<<联合类型>, <匹配类型>>
// 使用 Extract 提取符合条件的类型
type T = Extract<string | number | boolean, string | number>
NonNullable
基本写法:使用 NonNullable 排除 null
type <别名> = NonNullable<<类型>>
// 使用 NonNullable 排除 null 和 undefined
type T = NonNullable<string | null | undefined>
ReturnType 与 Parameters
基本写法:使用 ReturnType 获取返回类型
type <别名> = ReturnType<typeof <函数>>
// 从函数推断返回类型
function get_user() {
return { name: "Alice", age: 30 }
}
type User = ReturnType<typeof get_user>
基本写法:使用 Parameters 获取参数类型
type <别名> = Parameters<typeof <函数>>
// 从函数推断参数类型
function greet(name: string, age: number): void {}
type GreetParams = Parameters<typeof greet> // [string, number]
基本写法:使用 ConstructorParameters 获取构造函数参数
type <别名> = ConstructorParameters<typeof <类>>
// 从类推断构造函数参数类型
class User {
constructor(public name: string, public age: number) {}
}
type UserParams = ConstructorParameters<typeof User>
基本写法:使用 InstanceType 获取实例类型
type <别名> = InstanceType<typeof <类>>
// 从类推断实例类型
type UserInstance = InstanceType<typeof User>
条件类型与映射类型
换行写法:条件类型与映射类型组合
type <类型><<T>> = {
[P in keyof T]: T[P] extends <条件> ? <真类型> : <假类型>
}
// 条件类型与映射类型组合
type StringifyStrings<T> = {
[P in keyof T]: T[P] extends string ? string : never
}
递归条件类型
换行写法:递归条件类型
type <类型> = <T> extends Promise<infer <U>> ? <类型><<U>> : <T>
// 递归条件类型(处理嵌套 Promise)
type DeepAwaited<T> = T extends Promise<infer U> ? DeepAwaited<U> : T
换行写法:递归展平元组
type <类型> = <T> extends [infer <First>, ...infer <Rest>]
? <First> extends any[] ? [...<类型><<First>>, ...<类型><<Rest>>]
: [<First>, ...<类型><<Rest>>]
: []
// 递归展平嵌套元组
type Flatten<T extends any[]> = T extends [infer First, ...infer Rest]
? First extends any[] ? [...Flatten<First>, ...Flatten<Rest>]
: [First, ...Flatten<Rest>]
: []
条件类型推断函数重载
换行写法:推断重载函数返回类型
type <类型> = <T> extends (...args: any[]) => infer <R> ? <R> : never
// 推断重载函数的返回类型(取最后一个重载)
type GetOverloadReturn<T> = T extends (...args: any[]) => infer R ? R : never
infer 与模板字面量
换行写法:使用 infer 推断模板字面量
type <类型> = <S> extends \prefix_${infer
// 使用 infer 推断模板字面量中的类型
type RemovePrefix<S> = S extends `prefix_${infer T}` ? T : never
换行写法:推断字符串前缀
type <类型> = <S> extends \${infer
// 推断字符串前缀
type GetPrefix<S> = S extends `${infer Prefix}_suffix` ? Prefix : never
条件类型实战
换行写法:实现 DeepPartial
type <类型><<T>> = {
[P in keyof T]?: T[P] extends object ? <类型><T[P]> : T[P]
}
// 实现深度可选类型
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P]
}
换行写法:实现 DeepReadonly
type <类型><<T>> = {
readonly [P in keyof T]: T[P] extends object ? <类型><T[P]> : T[P]
}
// 实现深度只读类型
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]
}
换行写法:实现 Mutable
type <类型><<T>> = {
-readonly [P in keyof T]: T[P]
}
// 移除只读修饰符
type Mutable<T> = {
-readonly [P in keyof T]: T[P]
}
条件类型与 never
基本写法:使用 never 过滤
type <类型> = <T> extends <条件> ? <T> : never
// 使用 never 过滤不符合条件的类型
type FilterString<T> = T extends string ? T : never
基本写法:使用 never 过滤联合类型
type <别名> = <类型函数><<联合类型>>
// 使用 never 过滤联合类型
type Result = FilterString<string | number | boolean> // string
条件类型与函数推断
换行写法:推断异步函数返回类型
type <类型> = <T> extends (...args: any[]) => Promise<infer <R>> ? <R> : never
// 推断异步函数的返回类型
type AsyncReturnType<T> = T extends (...args: any[]) => Promise<infer R> ? R : never
换行写法:推断函数第一个参数类型
type <类型> = <T> extends (<参数>: infer <P>, ...args: any[]) => any ? <P> : never
// 推断函数第一个参数类型
type FirstParameter<T> = T extends (first: infer P, ...args: any[]) => any ? P : never