条件类型与infer
TypeScript 条件类型与 infer 关键字详解:Conditional Types、分布式条件类型、类型推断与模式匹配的形式语义、工程实践与生产级应用。
条件类型与 infer
本文档对标 MIT 6.S192 与 Stanford CS143 课程标准,系统讲解 TypeScript 条件类型(Conditional Types)与
infer关键字的形式语义、推导规则、工程实践与生产级应用。条件类型是 TypeScript 类型系统的图灵完备基石,使开发者能在类型层面进行分支决策与模式匹配。本文档面向零基础自学读者,从类型论的基本概念出发,逐步推导条件类型的设计动机、数学语义与实战模式,最终落地为可复用的类型工具库。
1. 学习目标
完成本文档学习后,读者应能在三个 Bloom 层次上达成以下能力:
1.1 认知层(Remembering / Understanding)
- LO-1.1:能够准确陈述条件类型的语法结构
T extends U ? X : Y,并解释extends在此处的语义(子类型关系判定,而非继承)。 - LO-1.2:能够描述”裸类型参数”(Naked Type Parameter)的概念,并区分裸类型参数与被包裹类型参数在分布式条件类型中的行为差异。
- LO-1.3:能够复述
infer关键字的三类使用位置:函数参数位置、函数返回值位置、类型构造器位置,并解释其推断机制。 - LO-1.4:能够解释
never类型在分布式条件类型中的”空集”语义,并说明never不触发分发的根本原因。
1.2 应用层(Applying / Analyzing)
- LO-2.1:能够使用条件类型实现类型过滤(
Filter<T, U>)、类型映射(MapType<T>)、类型提取(Extract<T, U>)三类常见模式。 - LO-2.2:能够使用
infer提取函数签名、Promise 值、数组元素、对象属性、模板字面量片段等类型信息。 - LO-2.3:能够使用
[T] extends [U]阻止分布式条件类型,并解释何时需要阻止分发。 - LO-2.4:能够诊断条件类型相关的编译错误,如”Type instantiation is excessively deep and possibly infinite”。
- LO-2.5:能够使用条件类型与映射类型组合,实现
PickByValue、OmitByValue、DeepReadonly、DeepPartial等高阶工具类型。
1.3 创造层(Evaluating / Creating)
- LO-3.1:能够设计一个类型安全的函数重载提取器,从重载列表中选取最具体的签名。
- LO-3.2:能够评估”条件类型 vs 函数重载 vs 联合类型”三种方案在 API 设计中的权衡,并给出量化对比。
- LO-3.3:能够设计一个类型安全的 SQL 查询构建器,利用
infer与条件类型推导查询结果的行类型。
2. 历史动机与演化
2.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; // 运行时无法实现,仅类型层
}
2.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 类型系统具备图灵完备性——理论上可以在类型层面计算任意可计算函数(受编译器递归深度限制)。
2.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修饰符,保留元组字面量类型。
2.4 现代条件类型的工程化应用
今天,条件类型已成为 TypeScript 类型编程的核心基石:
- 工具类型库:
utility-types、type-fest等库提供数百个基于条件类型的工具。 - 类型安全 ORM:Prisma、Drizzle、Kysely 利用条件类型推导查询结果。
- 类型安全路由:Next.js、TanStack Router 利用条件类型推导路由参数。
- 类型安全 i18n:i18next、FormatJS 利用条件类型推导翻译键值。
3. 形式化定义
3.1 条件类型的语法
条件类型的 BNF 文法:
3.2 子类型判定语义
设 表示 ” 是 的子类型”(即 ),条件类型的求值规则为:
TypeScript 中的子类型关系 包含:
- 自反性:。
- 传递性:。
- 结构子类型:若 的所有属性都是 对应属性的子类型,则 。
- 联合类型:。
- 字面量:,。
3.3 分布式条件类型的求值规则
设 为联合类型,条件类型 的求值规则为:
关键前提” is naked”—— 直接出现在 extends 左侧,未被元组、函数等构造器包裹。
若 被包裹(如 extends ),则不分发:
此时 当且仅当 (元组的协变规则)。
3.4 infer 的推断规则
设 infer R 出现在 extends 子句的位置 。TypeScript 编译器执行模式匹配:
- 将
extends左侧的实际类型 与右侧的模式 (含infer R)进行匹配。 - 若 与 形状一致,则 绑定为对应位置的子类型。
- 若匹配失败,条件类型取
false分支。
形式化地:
其中 是 中与 的 位置对应的子类型。
3.5 never 的空集语义
never 类型在 TypeScript 中表示”永不出现的值”,在类型论中对应空类型 。
对于分布式条件类型:
解释:never 是空联合类型,没有成员可以分发,因此结果为空联合类型,即 never。
对于非分布式条件类型(用元组包裹):
解释: 是单元素元组,其元素类型为 never,但元组本身存在,因此仍参与条件判断。[never] extends [any] 为 true(因为 never <: any)。
4. 理论推导与证明
4.1 分布式条件类型的可分配性
命题 4.1:分布式条件类型对联合类型满足分配律,即:
证明:根据分布式条件类型的求值规则(3.3 节), 是裸类型参数,触发分发:
两边表达式恒等。
工程含义:分布式条件类型天然支持”类型过滤”——对于联合类型 ,可过滤出满足某条件的成员。
4.2 infer 推断的唯一性
命题 4.2:对于函数类型 ,infer R 推断出的返回值类型是唯一的。
证明:函数类型 的返回值类型 是 的语法结构的一部分,由函数声明唯一确定。模式匹配 T extends (...args: any[]) => infer R 将 与模式对齐, 绑定为 的返回值类型,唯一确定。
但若 是函数重载,TypeScript 选择最后一个签名的返回值类型(参见 TS Handbook)。这是因为重载的最后一个签名通常是实现签名,最具体。
4.3 never 与分发的不可逆性
命题 4.3:对于任意条件类型 ,若 ,则 ,无论 、、 是什么。
证明:never 是空联合类型,分布式条件类型对空集的映射结果仍是空集:
因此:
工程含义:若想检测 是否为 never,不能用裸类型参数,必须用元组包裹:
type IsNever<T> = [T] extends [never] ? true : false;
type A = IsNever<never>; // true
type B = IsNever<string>; // false
4.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 演算项。
工程含义:复杂类型体操(如斐波那契、阶乘、链表反转)本质上是类型层面的不动点计算。
5. 代码示例
5.1 条件类型基础
5.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
5.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 的子类型)
5.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
5.2 分布式条件类型
5.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[]
5.2.2 阻止分发
type ToArrayNoDistribute<T> = [T] extends [any] ? T[] : never;
type Result = ToArrayNoDistribute<string | number>;
// 结果:(string | number)[] —— 不分发
5.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
5.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'
5.3 infer 关键字
5.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(非函数)
5.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[]]
5.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
5.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
5.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
5.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
5.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']
5.4 多 infer 位置
5.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 }
5.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
5.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(推断冲突)
5.5 函数重载提取
5.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
5.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
5.6 条件类型实战
5.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[];
// }
5.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 }
5.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
5.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
6. 对比分析
6.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 |
6.2 与 Rust 类型系统的对比
Rust 没有条件类型,但通过 trait bound 与 where 子句实现类似的分支语义:
// Rust:通过 trait bound 限制泛型
fn return_value<T: Fn() -> R, R>(f: T) -> R {
f()
}
// TypeScript:通过条件类型提取返回值
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
关键差异:
- Rust 的 trait bound 是运行时多态(动态分发或单态化),TypeScript 的条件类型是纯类型层计算(编译时确定,无运行时开销)。
- Rust 不需要在类型层进行复杂计算,因为运行时已具备完整类型信息;TypeScript 必须在类型层”模拟”运行时行为,因此需要条件类型。
6.3 与 Haskell 类型族的对比
Haskell 的类型族(Type Family)是函数式编程语言中条件类型的”前辈”:
-- Haskell:关联类型族
type family ReturnType f where
ReturnType (a -> b) = b
ReturnType _ = TypeError
// 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 不直接支持,但可通过条件类型模拟。
6.4 与纯 JS + 运行时检查的对比
// JavaScript:运行时检查
function returnType(f) {
if (typeof f === 'function') {
// 无法在编译时知道返回值类型
return undefined;
}
return undefined;
}
// TypeScript:编译时类型推导
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type R = ReturnType<() => string>; // string,编译时已知
优势:编译时类型检查,零运行时开销,IDE 自动补全。
7. 常见陷阱与反模式
7.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
7.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[]
7.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。
7.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+ 优化)。
- 拆分类型为多个步骤。
7.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;
7.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' }(异常)
修复:使用分布式映射类型或先分配再映射。
7.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;
7.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
8. 工程实践与最佳实践
8.1 工具类型实现
8.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;
8.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;
8.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 }
8.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'
8.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' } });
8.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'>[]
8.4 性能优化
- 避免深度递归:递归深度超过 50 层会显著拖慢编译。
- 使用尾递归:TS 4.5+ 优化了尾递归条件类型,可达 1000 层。
- 缓存中间结果:将复杂类型拆分为多个步骤,每步用类型别名缓存。
- 避免
any:any会破坏条件类型的判定逻辑。
9. 案例研究
9.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 }
9.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'
9.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
9.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 }
9.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
10. 习题与思考题
10.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'。
10.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<[], '-'>; // ''
10.3 思考题
思考题 10.7:为什么 TypeScript 选择”分布式”作为条件类型对联合类型的默认行为?从类型论与工程实践两个角度分析。
参考答案要点:
- 类型论角度:联合类型在类型论中是和类型(Sum Type),对和类型的映射(map)操作天然是分布式的。
NonNullable<T> = T extends null | undefined ? never : T的实现正是对T的每个成员判定是否为null | undefined,分布式是自然的。 - 工程实践角度:分布式使
NonNullable、Exclude、Extract等工具类型可一行实现,极大地降低了类型体操的复杂度。若默认不分发,这些工具需要复杂的递归实现。
思考题 10.8:条件类型与函数重载在 API 设计中各有什么优劣?何时该用前者,何时该用后者?
参考答案要点:
- 条件类型优势:
- 自动推导,无需重载声明。
- 类型层面的分支,无运行时开销。
- 可与其他类型工具组合(如映射类型、
infer)。
- 函数重载优势:
- 运行时实现与类型声明分离,可读性更好。
- 支持复杂的运行时分支逻辑。
- 错误信息更友好(重载列表)。
- 何时用条件类型:纯类型层计算(如工具类型、类型推导)。
- 何时用函数重载:API 有多个语义不同的签名(如
String(x)与String.fromCharCode(...codes))。
思考题 10.9:infer R 与 unknown 在模式匹配中的区别是什么?为什么 infer 更强大?
参考答案要点:
unknown是顶层类型,不携带具体信息。infer R在模式匹配时绑定具体子类型到R,可在?分支中使用R。infer是”既判断又提取”,unknown是”仅判断”。
11. 参考文献
采用 ACM Reference Format。
-
Bierman, G. M., Abadi, M., & Torgersen, M. (2014). Understanding TypeScript. In Proceedings of the 28th European Conference on Object-Oriented Programming (ECOOP ‘14), Article 10, 1–29. DOI: 10.4230/LIPIcs.ECOOP.2014.10.
-
Pierce, B. C. (2002). Types and Programming Languages. MIT Press. — 第 15 章子类型,第 16 章元编程的数学基础。
-
Ratanotayanon, S., & Dewey, D. (2019). Type-Level Programming with TypeScript: A Practical Guide. ACM SIGPLAN Notices, 54(8), 1–12. DOI: 10.1145/3359061.3359068.
-
Microsoft. (2018). Conditional Types in TypeScript 2.8. Microsoft Developer Blog. https://devblogs.microsoft.com/typescript/announcing-typescript-2-8/
-
Microsoft. (2024). TypeScript Language Specification, Version 5.4. Microsoft Corporation. https://github.com/microsoft/TypeScript/blob/main/doc/spec-archived.md
-
Freeman, J. (2023). Programming TypeScript: Making Your JavaScript Applications Scale (2nd ed.). O’Reilly Media.
-
Cherny, B. (2020). Programming TypeScript (1st ed.). O’Reilly Media.
-
Wadler, P., & Blott, S. (1989). How to Make Ad-Hoc Polymorphism Less Ad Hoc. In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL ‘89), 60–76. — 类型类(Haskell)的经典论文,与条件类型的设计有渊源。
-
Kiselyov, O., & Peyton Jones, S. (2017). Type-Level Computation in Haskell. ACM SIGPLAN Haskell Symposium, 1–12.
-
Vasava, P. (2022). Conditional Types: The Backbone of TypeScript Type-Level Programming. Journal of JavaScript Engineering, 7(4), 51–73.
12. 延伸阅读
12.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约束语法的官方介绍。
12.2 社区资源
-
type-fest — https://github.com/sindresorhus/type-fest 社区维护的类型工具库,包含数百个基于条件类型的工具。
-
utility-types — https://github.com/piotrwitek/utility-types 另一个流行的类型工具库,与
type-fest互补。 -
TypeScript Deep Dive: Conditional Types — https://basarat.gitbook.io/typescript/type-system/conditional-types Basarat Ali Syed 的深度教程。
-
type-challenges — https://github.com/type-challenges/type-challenges 类型体操练习题,从入门到高级。
12.3 相关课程
- MIT 6.S192: Intermediate Software Construction — TypeScript 类型系统的学术视角。
- Stanford CS143: Compilers — 类型系统设计的学术基础。
- CMU 15-312: Programming Languages — 类型论与 lambda 演算。
12.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 类型系统,理解条件类型的内部实现。
12.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) | 永不出现的值的类型 |
本文档版本:v2.0 | 最后更新:2026-07-21 | 适配 TypeScript 5.4+