类型推断infer扩展
TypeScript 中 infer 关键字的形式语义、推导规则与生产级应用——从函数返回值到模板字面量、从 Promise 解包到 AST 元编程
类型推断 infer 扩展
0. 引言:为什么 infer 是 TypeScript 类型系统的分水岭
infer 关键字于 2018 年 3 月随 TypeScript 2.8 发布,由 Anders Hejlsberg 在 Pull Request #21496 中引入。在这之前,TypeScript 的类型系统是一个只读的结构类型系统——开发者可以描述类型、可以检查类型,但无法在类型层”提取”类型。infer 的出现把 TypeScript 类型系统从”被动描述”推进到”主动计算”,使其具备了一阶类型层编程的能力。
infer 的工程意义可类比为运行时的解构赋值:正如 const { x } = obj 在运行时把对象的属性提取为变量,T extends { x: infer U } ? U : never 在类型层把对象属性的类型”提取”为类型变量。这种类型层的解构能力是 TypeScript 工具类型(utility types)体系的基石——ReturnType、Parameters、Awaited、ConstructorParameters 等内置工具类型无一不是 infer 的应用。
本教程将以海外知名高校(MIT 6.826、Stanford CS143、CMU 15-410)教授类型系统的严谨度,系统讲解 infer 的形式语义、推导规则、工程实践与陷阱。读完本教程,读者应能独立设计并实现生产级的类型工具,并能对 infer 的设计动机与替代方案给出工程论证。
1. 历史动机与技术演进
1.1 2.8 之前的痛点:类型层”只读”
在 TypeScript 2.8 之前,开发者若想提取函数的返回类型,必须显式声明一个中间类型:
// 2.8 之前的写法
function getLength(s: string): number { return s.length; }
type GetLengthReturn = ReturnType<typeof getLength>; // 错误:ReturnType 不存在
// 只能手动复制
type GetLengthReturn = number;
这种方式不仅冗长,更严重的是无法泛化——若函数签名变更,手动复制的类型就会过期。社区在 2014-2017 年间提交了大量 issue(如 #6606、#10755、#12929),要求引入”类型提取”能力。Anders Hejlsberg 在 #21496 中给出的设计回应是:
“Add a way to extract a type from another type via conditional types and the
inferkeyword.”
设计灵感来自三个来源:
- Haskell 的类型族(type families):但 Haskell 的类型族是闭合的、需要显式声明,与 TypeScript 的开放世界不兼容。
- Scala 的抽象类型成员(abstract type members):Scala 允许在 trait 中声明
type T,由具体实现填充;TypeScript 选择更激进的方案——让编译器自动填充。 - C++ 的模板参数推导:C++17 的
template <class T> void f(T)中T由编译器推导,TypeScript 借鉴了”编译器推导类型变量”的思想。
1.2 时间线:从 2.8 到 5.x
下表列出了 infer 关键能力在 TypeScript 各版本中的演进:
| 版本 | 年份 | 关键能力 | PR / Issue |
|---|---|---|---|
| 2.8 | 2018-03 | infer 关键字在条件类型中引入 | #21496 |
| 3.0 | 2018-07 | 元组中的 rest 元素支持 infer | #24897 |
| 3.4 | 2019-03 | infer T extends X 约束推断变量 | #25260 |
| 4.0 | 2020-08 | 元组与 rest 元素的 infer 推导更精确 | #39094 |
| 4.1 | 2020-11 | 模板字面量类型支持 infer | #40336 |
| 4.5 | 2021-11 | 内置 Awaited<T> 类型 | #46052 |
| 4.7 | 2022-05 | infer extends 约束语法稳定化 | #49385 |
| 5.0 | 2023-03 | const 类型参数与 infer 协同 | #51865 |
| 5.4 | 2024-03 | NoInfer<T> 工具类型 | #57499 |
1.3 原作者:Anders Hejlsberg
Anders Hejlsberg 是丹麦裔计算机科学家,微软技术院士(Technical Fellow),Turbo Pascal、Delphi、C# 与 TypeScript 的首席架构师。他在 1996 年加入微软,主导设计了 .NET 平台的两大语言(C# 与 TypeScript)。Hejlsberg 的设计哲学是”让编译器为开发者服务”——这一哲学直接体现在 infer 的设计上:开发者只需声明”我想提取这个类型”,编译器自动完成推导工作,而不需要像 Haskell 那样显式声明类型类实例。
2. 形式化定义
2.1 语法
infer 的语法在 TypeScript 2.8 引入时仅有一种形式:
ConditionalType ::= T extends InferPattern ? Type1 : Type2
InferPattern ::= ... <contains one or more `infer U` declarations>
TypeScript 4.7 起扩展为支持约束的形式:
InferVarDecl ::= infer Identifier [extends ConstraintType]
2.2 类型论形式语义
设 为类型环境, 为类型判断关系,TypeScript 的条件类型推导规则可形式化为:
读作:若在类型环境 下 与 是良构类型,且合一算法能找到替换 使 ,则条件类型的结果为 ;否则为 。其中 是 infer 引入的存在量化变量, 与 是条件类型的两个分支。
2.3 合一算法简介
TypeScript 编译器使用的合一算法是 Robinson 合一的变体,其核心思想是:
- 将待匹配的两个类型分别记为 与 ,其中 含有存在变量 。
- 递归地比较 与 的结构:
- 若 (结构相同),返回空替换 。
- 若 含 ,将 绑定到 对应位置的子类型。
- 若 与 的构造器不同(如
PromisevsArray),失败。
- 合一过程可能产生多重替换,需要进行组合与一致性检查。
形式化地,合一函数 满足:
其中 是最一般统一元(Most General Unifier, MGU)。
2.4 与 System F 的关系
TypeScript 的类型系统可视为 System F<:sub>(带子类型的 System F)的扩展。infer 引入的存在量化变量对应 System F 中的存在类型 ,但 TypeScript 不要求显式打包(pack)与解包(unpack),而是由编译器在条件类型求值时自动完成。
形式化对比:
| 概念 | System F<:sub | TypeScript |
|---|---|---|
| 类型抽象 | <T>(t: T) => ... | |
| 类型应用 | f<T>(...) | |
| 存在类型 | T extends { x: infer U } ? ... | |
| 合一 | 手动 pack/unpack | 编译器自动 |
3. 理论推导
3.1 类型推导规则
下面给出 infer 的核心推导规则,使用类型论中常见的推断横线(inference rule)表示法:
规则 R1:基本提取
读作:若 是 Promise<τ>,则 T extends Promise<infer α> ? α : never 求值为 τ。
规则 R2:失败分支
读作:若 不是 Promise,则条件类型走 else 分支,结果为 never。
规则 R3:分布式分发
其中 。这是分布式条件类型的关键规则:当输入是裸联合类型时,对每个成员单独应用规则。
规则 R4:递归推导
读作:Awaited<Promise<Promise<T>>> 递归求值为 Awaited<T>,最终为 T。
3.2 子类型关系
infer 引入的变量默认具有”上限类型”——即 infer U 中的 U 的类型上界是其在 extends 子句右侧出现位置的类型。例如:
type F<T> = T extends (x: infer U) => void ? U : never;
// U 的上界是 unknown
TypeScript 4.7 起,可显式约束 infer 变量:
type F<T> = T extends (x: infer U extends string) => void ? U : never;
// U 的上界是 string
约束的语义可形式化为:
其中 是约束类型, 表示求解后的 必须是 的子类型。
3.3 复杂度分析
infer 的合一算法在最坏情况下的复杂度为 ,其中 是类型表达式的大小。对于深度嵌套的递归类型(如 DeepReadonly),实际复杂度可达 ,其中 是递归深度。
TypeScript 4.5 起对尾递归条件类型做了优化,将原本的 栈空间复杂度降为 。但非尾递归的深度仍受 100 层硬性限制约束。
启发式估算:单个复杂 infer 表达式的编译时间通常在 1-10ms 量级;当类型参数数量超过 50 时,可能触发 TypeScript 编译器的 Expression produces a union type that is too complex to represent 错误。
4. 函数类型推断
4.1 提取返回类型
ReturnType<T> 是 infer 最经典的应用,它从一个函数类型中提取返回值类型:
/**
* 提取函数类型 T 的返回类型
* 若 T 不是函数类型,返回 never
*
* @example
* type R1 = ReturnType<() => string>; // string
* type R2 = ReturnType<(x: number) => void>; // void
* type R3 = ReturnType<typeof Array.isArray>; // boolean
*/
type ReturnType<T extends (...args: any) => any>
= T extends (...args: any) => infer R ? R : never;
实现要点:
T extends (...args: any) => any是泛型约束,确保传入的是函数类型。(...args: any) => infer R中,infer R在返回值位置上声明存在变量,由编译器求解为函数的实际返回类型。- 若 T 不是函数类型,走 else 分支返回
never。
4.2 提取参数列表
/**
* 提取函数类型 T 的参数列表(元组形式)
*
* @example
* type P1 = Parameters<(x: number, y: string) => void>; // [number, string]
* type P2 = Parameters<(a: boolean) => void>; // [boolean]
* type P3 = Parameters<(...args: number[]) => void>; // number[]
*/
type Parameters<T extends (...args: any) => any>
= T extends (...args: infer P) => any ? P : never;
4.3 提取首尾参数
/**
* 提取函数的第一个参数类型
*
* @example
* type F = FirstParameter<(x: number, y: string) => void>; // number
*/
type FirstParameter<T extends (...args: any) => any>
= T extends (first: infer F, ...rest: any[]) => any ? F : never;
/**
* 提取函数的最后一个参数类型
* 利用了 TypeScript 4.0+ 支持的元组尾部的 rest 元素推断
*
* @example
* type L = LastParameter<(x: number, y: string) => void>; // string
*/
type LastParameter<T extends (...args: any) => any>
= T extends (...args: [...any[], infer L]) => any ? L : never;
4.4 提取构造函数参数与实例类型
/**
* 提取构造函数类型 T 的实例类型
*
* @example
* class Person { constructor(public name: string) {} }
* type P = InstanceType<typeof Person>; // Person
*/
type InstanceType<T extends abstract new (...args: any) => any>
= T extends abstract new (...args: any) => infer R ? R : any;
/**
* 提取构造函数的参数列表
*
* @example
* class Person { constructor(public name: string, public age: number) {} }
* type P = ConstructorParameters<typeof Person>; // [name: string, age: number]
*/
type ConstructorParameters<T extends abstract new (...args: any) => any>
= T extends abstract new (...args: infer P) => any ? P : never;
注意 abstract new 的使用——这是为了支持抽象类的构造函数类型。
4.5 异步函数与 Promise 解包
/**
* 递归地解包 Promise 类型
* - Promise<Promise<T>> -> T
* - Promise<T> -> T
* - T (非 Promise) -> T
*
* 这是 TypeScript 4.5+ 内置 Awaited<T> 的等价实现
*/
type Awaited<T> =
T extends null | undefined
? T
: T extends object & { then(onfulfilled: infer F, ...args: infer _): any }
? F extends (value: infer V, ...args: infer _) => any
? Awaited<V>
: never
: T;
// 实战:提取异步函数的最终返回值
type AsyncReturnType<T extends (...args: any) => Promise<any>>
= T extends (...args: any) => Promise<infer R> ? Awaited<R> : never;
type R = AsyncReturnType<() => Promise<Promise<number>>>; // number
4.6 函数重载的处理
TypeScript 4.7+ 对重载函数的 infer 推导有特殊处理:仅使用最后一个签名进行推导。这是因为在 TypeScript 的重载解析规则中,最后一个签名是实现签名,最准确反映函数行为。
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
type P = Parameters<typeof f>; // [x: string | number]
若想提取所有重载的返回类型,需要使用 OverloadReturn 工具:
/**
* 提取重载函数的所有返回类型(联合形式)
*
* 实现思路:
* - 利用 TypeScript 内部对重载签名的处理顺序
* - 通过递归条件类型遍历所有签名
*/
type OverloadReturn<T> =
T extends {
(...args: infer A1): infer R1;
(...args: infer A2): infer R2;
(...args: infer A3): infer R3;
(...args: infer A4): infer R4;
} ? R1 | R2 | R3 | R4
: T extends {
(...args: infer A1): infer R1;
(...args: infer A2): infer R2;
(...args: infer A3): infer R3;
} ? R1 | R2 | R3
: T extends {
(...args: infer A1): infer R1;
(...args: infer A2): infer R2;
} ? R1 | R2
: T extends (...args: any) => infer R ? R : never;
5. 元组与数组推断
5.1 提取首尾元素
/**
* 提取元组的首元素类型
*
* @example
* type H = Head<[number, string, boolean]>; // number
*/
type Head<T extends readonly any[]>
= T extends readonly [infer H, ...any[]] ? H : never;
/**
* 提取元组的尾元组(去除首元素)
*
* @example
* type T = Tail<[number, string, boolean]>; // [string, boolean]
*/
type Tail<T extends readonly any[]>
= T extends readonly [any, ...infer R] ? R : never;
/**
* 提取元组的尾元素(最后一个)
* 利用 TypeScript 4.0+ 的元组尾部 rest 推断
*
* @example
* type L = Last<[number, string, boolean]>; // boolean
*/
type Last<T extends readonly any[]>
= T extends readonly [...any[], infer L] ? L : never;
注意 readonly 修饰符——它是为了支持 readonly 元组(如 as const 产生的元组)。
5.2 提取数组元素类型
/**
* 提取数组类型的元素类型
*
* @example
* type E1 = ElementOf<number[]>; // number
* type E2 = ElementOf<readonly string[]>; // string
* type E3 = ElementOf<[boolean, ...]>; // boolean
*/
type ElementOf<T>
= T extends readonly (infer E)[] ? E : never;
5.3 元组反转与切片
/**
* 反转元组
* 使用尾递归以支持长元组
*
* @example
* type R = Reverse<[1, 2, 3]>; // [3, 2, 1]
*/
type Reverse<T extends readonly any[], Acc extends readonly any[] = []>
= T extends readonly [infer H, ...infer R]
? Reverse<R, [H, ...Acc]>
: Acc;
/**
* 元组切片(从位置 Start 到末尾)
*
* @example
* type S = Slice<[1, 2, 3, 4], 2>; // [3, 4]
*/
type Slice<
T extends readonly any[],
Start extends number,
Acc extends readonly any[] = []
> = Acc['length'] extends Start
? T
: T extends readonly [infer H, ...infer R]
? Slice<R, Start, [...Acc, H]>
: T;
5.4 元组拉平
/**
* 拉平嵌套元组
*
* @example
* type F = Flatten<[1, [2, 3], 4]>; // [1, 2, 3, 4]
*/
type Flatten<T extends readonly any[]> =
T extends readonly [infer H, ...infer R]
? H extends readonly any[]
? [...Flatten<H>, ...Flatten<R>]
: [H, ...Flatten<R>]
: [];
/**
* 深度拉平(支持任意嵌套深度)
*
* @example
* type D = DeepFlatten<[1, [2, [3, [4]]]]>; // [1, 2, 3, 4]
*/
type DeepFlatten<T extends readonly any[]> =
T extends readonly [infer H, ...infer R]
? H extends readonly any[]
? [...DeepFlatten<H>, ...DeepFlatten<R>]
: [H, ...DeepFlatten<R>]
: [];
5.5 元组过滤
/**
* 从元组中过滤掉类型 F 的元素
*
* @example
* type F = Filter<[1, 'a', 2, 'b'], number>; // [1, 2]
*/
type Filter<T extends readonly any[], F, Acc extends readonly any[] = []> =
T extends readonly [infer H, ...infer R]
? H extends F
? Filter<R, F, [...Acc, H]>
: Filter<R, F, Acc>
: Acc;
6. 字符串模板推断
6.1 基础字符串分割与拼接
/**
* 删除字符串左侧的空白字符
* 利用模板字面量类型与递归 infer
*
* @example
* type S = TrimLeft<' hello'>; // 'hello'
*/
type TrimLeft<S extends string>
= S extends ` ${infer Rest}` ? TrimLeft<Rest> : S;
/**
* 删除字符串右侧的空白字符
*/
type TrimRight<S extends string>
= S extends `${infer Rest} ` ? TrimRight<Rest> : S;
/**
* 删除字符串两侧的空白字符
*/
type Trim<S extends string> = TrimLeft<TrimRight<S>>;
/**
* 按分隔符 D 分割字符串 S
*
* @example
* type R1 = Split<'a,b,c', ','>; // ['a', 'b', 'c']
* type R2 = Split<'hello', ','>; // ['hello']
*/
type Split<S extends string, D extends string> =
D extends ''
? S extends `${infer C}${infer Rest}`
? [C, ...Split<Rest, D>]
: S extends '' ? [] : [S]
: S extends `${infer Head}${D}${infer Tail}`
? [Head, ...Split<Tail, D>]
: [S];
/**
* 用分隔符 D 拼接字符串元组 T
*
* @example
* type R = Join<['a', 'b', 'c'], '-'>; // 'a-b-c'
*/
type Join<T extends readonly string[], D extends string> =
T extends readonly [infer Head extends string, ...infer Rest extends string[]]
? Rest extends []
? Head
: `${Head}${D}${Join<Rest, D>}`
: '';
6.2 字符串替换
/**
* 将字符串 S 中所有 From 替换为 To
*
* @example
* type R = Replace<'hello world', 'o', '0'>; // 'hell0 w0rld'
*/
type ReplaceAll<S extends string, From extends string, To extends string> =
From extends ''
? S
: S extends `${infer Before}${From}${infer After}`
? `${Before}${To}${ReplaceAll<After, From, To>}`
: S;
6.3 大小写转换
/**
* 将字符串首字母大写(TypeScript 内置)
* type R = Capitalize<'hello'>; // 'Hello'
*/
/**
* 将字符串中的每个单词首字母大写
*
* @example
* type R = CapitalizeWords<'hello world foo'>; // 'Hello World Foo'
*/
type CapitalizeWords<S extends string, Acc extends string = ''> =
S extends `${infer First}${infer Rest}`
? Acc extends ''
? CapitalizeWords<Rest, `${Capitalize<First>}`>
: CapitalizeWords<Rest, `${Acc}${First}`>
: Acc;
6.4 提取路径参数
/**
* 从路由路径中提取参数名
*
* @example
* type P = RouteParams<'/users/:userId/posts/:postId'>;
* // { userId: string; postId: string }
*/
type RouteParams<S extends string> =
S extends `${infer _Start}:${infer Param}/${infer Rest}`
? { [K in Param | keyof RouteParams<Rest>]: string }
: S extends `${infer _Start}:${infer Param}`
? { [K in Param]: string }
: {};
/**
* 提取所有参数名为元组
*
* @example
* type P = RouteParamList<'/users/:userId/posts/:postId'>;
* // ['userId', 'postId']
*/
type RouteParamList<S extends string> =
S extends `${infer _Start}:${infer Param}/${infer Rest}`
? [Param, ...RouteParamList<`/${Rest}`>]
: S extends `${infer _Start}:${infer Param}`
? [Param]
: [];
6.5 URL 查询字符串解析
/**
* 解析 URL 查询字符串为对象类型
*
* @example
* type Q = ParseQueryString<'a=1&b=2&c=3'>;
* // { a: '1'; b: '2'; c: '3' }
*/
type ParseQueryString<S extends string> =
S extends `${infer K}=${infer V}&${infer Rest}`
? Merge<Record<K, V>, ParseQueryString<Rest>>
: S extends `${infer K}=${infer V}`
? Record<K, V>
: S extends `${infer K}&${infer Rest}`
? Merge<Record<K, string>, ParseQueryString<Rest>>
: S extends `${infer K}`
? S extends '' ? {} : Record<K, string>
: {};
type Merge<A, B> = { [K in keyof A | keyof B]: K extends keyof B ? B[K] : K extends keyof A ? A[K] : never };
6.6 实现 CamelCase / KebabCase / SnakeCase 转换
/**
* 将 kebab-case 或 snake_case 转为 camelCase
*
* @example
* type R1 = CamelCase<'hello-world'>; // 'helloWorld'
* type R2 = CamelCase<'hello_world'>; // 'helloWorld'
*/
type CamelCase<S extends string> =
S extends `${infer H}-${infer Rest}`
? `${H}${Capitalize<CamelCase<Rest>>}`
: S extends `${infer H}_${infer Rest}`
? `${H}${Capitalize<CamelCase<Rest>>}`
: S;
/**
* 将 camelCase 转为 kebab-case
*
* @example
* type R = KebabCase<'helloWorld'>; // 'hello-world'
*/
type KebabCase<S extends string, Acc extends string = ''> =
S extends `${infer First}${infer Rest}`
? First extends Uppercase<First>
? First extends Lowercase<First>
? KebabCase<Rest, `${Acc}${First}`>
: KebabCase<Rest, `${Acc}-${Lowercase<First>}`>
: KebabCase<Rest, `${Acc}${First}`>
: Acc;
7. 对象类型推断
7.1 按值过滤属性
/**
* 选取 T 中值类型为 V 的属性
*
* @example
* type T = { a: string; b: number; c: string; d: boolean };
* type R = PickByValue<T, string>; // { a: string; c: string }
*/
type PickByValue<T, V> = {
[K in keyof T as T[K] extends V ? K : never]: T[K];
};
/**
* 排除 T 中值类型为 V 的属性
*
* @example
* type T = { a: string; b: number; c: string };
* type R = OmitByValue<T, string>; // { b: number }
*/
type OmitByValue<T, V> = {
[K in keyof T as T[K] extends V ? never : K]: T[K];
};
7.2 提取可选/必选属性
/**
* 提取 T 中的可选属性
*
* @example
* type T = { a: string; b?: number; c?: boolean };
* type R = OptionalKeys<T>; // 'b' | 'c'
*/
type OptionalKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
}[keyof T];
/**
* 提取 T 中的必选属性
*/
type RequiredKeys<T> = {
[K in keyof T]-?: {} extends Pick<T, K> ? never : K;
}[keyof T];
/**
* 提取 T 中类型为函数的属性
*
* @example
* type T = { id: number; onClick: () => void; onHover: () => void };
* type R = FunctionPropertyNames<T>; // 'onClick' | 'onHover'
*/
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
7.3 提取可空属性
/**
* 提取 T 中类型包含 null/undefined 的属性名
*
* @example
* type T = { a: string; b: string | null; c?: number };
* type R = NullableKeys<T>; // 'b' | 'c'
*/
type NullableKeys<T> = {
[K in keyof T]-?: undefined extends T[K]
? K
: null extends T[K]
? K
: never;
}[keyof T];
7.4 深层属性访问
/**
* 获取对象 T 中由点分隔路径 P 指定的深层属性类型
*
* @example
* type T = { user: { profile: { age: number } } };
* type R = DeepGet<T, 'user.profile.age'>; // number
*/
type DeepGet<T, P extends string> =
P extends `${infer K}.${infer Rest}`
? K extends keyof T
? DeepGet<T[K], Rest>
: never
: P extends keyof T
? T[P]
: never;
/**
* 设置对象 T 中由点分隔路径 P 指定的深层属性类型
* 同时验证值的类型
*/
type DeepSet<T, P extends string, V> =
P extends `${infer K}.${infer Rest}`
? K extends keyof T
? Omit<T, K> & { [Key in K]: DeepSet<T[K], Rest, V> }
: T
: P extends keyof T
? Omit<T, P> & { [Key in P]: V }
: T;
7.5 不可变与可变转换
/**
* 深度只读
* 与官方 Readonly 不同,DeepReadonly 递归地使所有嵌套属性只读
*
* 注意:必须处理函数、Date、Map 等内置对象,否则会破坏其调用语义
*/
type DeepReadonly<T> =
T extends (...args: any[]) => any
? T
: T extends object
? T extends Map<any, any> | Set<any> | Date | RegExp | ArrayBuffer
? T
: { readonly [K in keyof T]: DeepReadonly<T[K]> }
: T;
/**
* 深度可变(DeepReadonly 的逆操作)
*/
type DeepMutable<T> =
T extends (...args: any[]) => any
? T
: T extends object
? T extends Map<any, any> | Set<any> | Date | RegExp | ArrayBuffer
? T
: { -readonly [K in keyof T]: DeepMutable<T[K]> }
: T;
/**
* 深度 Partial
*/
type DeepPartial<T> =
T extends (...args: any[]) => any
? T
: T extends object
? { [K in keyof T]?: DeepPartial<T[K]> }
: T;
/**
* 深度 Required
*/
type DeepRequired<T> =
T extends (...args: any[]) => any
? T
: T extends object
? { [K in keyof T]-?: DeepRequired<T[K]> }
: T;
8. 高级应用:infer 在 AST 与元编程中的实践
8.1 实现一个类型安全的 SQL 解析器
/**
* 在类型层解析 SQL SELECT 语句
* 仅支持基础语法:SELECT <fields> FROM <table>
*
* @example
* type R = ParseSQL<'SELECT id, name FROM users'>;
* // { fields: ['id', 'name']; table: 'users' }
*/
type Trim<S extends string> =
S extends ` ${infer Rest}` ? Trim<Rest>
: S extends `${infer Rest} ` ? Trim<Rest>
: S;
type ParseFields<S extends string> =
S extends ''
? []
: S extends `${infer F}, ${infer Rest}`
? [Trim<F>, ...ParseFields<Rest>]
: S extends `${infer F}`
? [Trim<F>]
: [];
type ParseSQL<S extends string> =
S extends `SELECT ${infer Fields} FROM ${infer Table}`
? { fields: ParseFields<Fields>; table: Trim<Table> }
: never;
type Q = ParseSQL<'SELECT id, name FROM users'>;
// { fields: ['id', 'name']; table: 'users' }
8.2 实现 JSON Schema 到 TypeScript 类型的转换
/**
* 将简化的 JSON Schema 类型转换为 TypeScript 类型
*
* 仅支持基础类型:string, number, boolean, null, array, object
*/
type JsonSchemaToType<S> =
S extends { type: 'string' }
? string
: S extends { type: 'number' | 'integer' }
? number
: S extends { type: 'boolean' }
? boolean
: S extends { type: 'null' }
? null
: S extends { type: 'array'; items: infer I }
? JsonSchemaToType<I>[]
: S extends { type: 'object'; properties: infer P; required?: infer R }
? { [K in keyof P as K extends (R extends readonly (infer U)[] ? U : never) ? K : never]-?: JsonSchemaToType<P[K]> }
& { [K in keyof P as K extends (R extends readonly (infer U)[] ? U : never) ? never : K]?: JsonSchemaToType<P[K]> }
: never;
type Schema = {
type: 'object';
properties: {
name: { type: 'string' };
age: { type: 'number' };
email: { type: 'string' };
};
required: ['name', 'age'];
};
type Result = JsonSchemaToType<Schema>;
// { name: string; age: number; email?: string }
8.3 实现 Promise 链的类型推导
/**
* 模拟 Promise.then 链的类型推导
*/
type ThenReturn<T, F> =
T extends Promise<infer R>
? F extends (value: R) => infer Next
? Next extends Promise<infer NN>
? Promise<NN>
: Promise<Next>
: never
: never;
type R1 = ThenReturn<Promise<number>, (x: number) => string>; // Promise<string>
type R2 = ThenReturn<Promise<number>, (x: number) => Promise<boolean>>; // Promise<boolean>
8.4 类型安全的 JSON 路径查询
/**
* 根据 JSONPath 风格的字符串路径获取嵌套类型
* 支持 . 与 [] 两种语法
*
* @example
* type T = { users: [{ posts: [{ title: string }] }] };
* type R = JsonPath<T, 'users[0].posts[0].title'>; // string
*/
type JsonPath<T, P extends string> =
P extends `[${infer I}]${infer Rest}`
? T extends readonly (infer E)[]
? I extends `${number}`
? JsonPath<E, Rest>
: never
: never
: P extends `${infer K}[${infer Rest}`
? K extends keyof T
? JsonPath<T[K], `[${Rest}`>
: never
: P extends `${infer K}.${infer Rest}`
? K extends keyof T
? JsonPath<T[K], Rest>
: never
: P extends `${infer K}`
? K extends keyof T
? T[K]
: never
: never;
9. 与其他语言的对比
9.1 与 Haskell 的对比
Haskell 不需要 infer 关键字,因为 Haskell 的类型推导是基于 Hindley-Milner(HM)算法的全局推导——编译器自动推导所有类型变量。
-- Haskell: 全局类型推导
f x = x + 1 -- f :: Num a => a -> a
g = f . length -- g :: [a] -> Int
TypeScript 不能使用 HM 算法的原因:
- 结构性类型:HM 假设名义类型,TypeScript 是结构性的。
- 子类型:HM 不支持子类型,TypeScript 有复杂的子类型关系。
- 重载:JavaScript 函数经常重载,HM 不支持。
- 动态特性:JavaScript 的
any、unknown、never等类型在 HM 中没有对应。
Haskell 通过 类型类(type classes) 实现 infer 的某些功能:
class ReturnType f where
type Return f :: *
instance ReturnType (a -> b) where
type Return (a -> b) = b
这是类型族(type families)的应用,相当于 TypeScript 的 infer,但需要显式声明实例。
9.2 与 Rust 的对比
Rust 不需要 infer,因为 Rust 的类型推导发生在表达式层,而非类型层。Rust 的 trait 系统类似于 Haskell 的类型类。
// Rust: 类型推导发生在表达式层
let x = 5; // i32
let y = x + 1; // i32
let z: u64 = x as u64; // 显式转换
Rust 的关联类型(associated types)类似于 TypeScript 的 infer:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
fn sum<I: Iterator<Item = i32>>(iter: I) -> i32 {
iter.fold(0, |a, b| a + b)
}
TypeScript 没有 trait 系统,因此需要 infer 来提取关联类型。
9.3 与 Scala 的对比
Scala 的抽象类型成员(abstract type members)与 infer 类似:
trait Container {
type T
def value: T
}
val c = new Container {
type T = Int
def value = 42
}
val v: c.T = c.value // 42
Scala 3 的 given/using 进一步简化了类型类解析:
trait Show[A] {
def show(a: A): String
}
given Show[Int] with
def show(a: Int) = a.toString
def print[A](a: A)(using s: Show[A]) = println(s.show(a))
TypeScript 没有 given/using 机制,所有”类型类”必须通过参数显式传递。
9.4 与 C++ 的对比
C++ 的模板参数推导与 infer 在精神上相似:
template <typename T>
void f(T x) { /* T 由编译器推导 */ }
template <typename T>
struct ReturnType;
template <typename R, typename... Args>
struct ReturnType<R(Args...)> {
using type = R;
};
C++17 起,auto 与 decltype 提供了更强的推导能力:
auto f = [](int x) { return x + 1.0; }; // f: int -> double
decltype(f(0)) y = 0; // y: double
但 C++ 的推导发生在编译期模板实例化,与 TypeScript 的编译期类型检查在工程上不同。
10. 常见陷阱与修复
10.1 陷阱一:分布式条件类型导致的 infer 失效
// 错误:分布式条件类型导致 never 被过滤
type BadReturnType<T> = T extends (...args: any) => infer R ? R : never;
type R1 = BadReturnType<never>; // never(期望:never)
type R2 = BadReturnType<string | number>; // never(期望:never,因为 string 不是函数)
// 修复:用 [T] 阻止分布式
type GoodReturnType<T> = [T] extends [(...args: any) => infer R] ? R : never;
10.2 陷阱二:infer 在循环引用中的失效
// 错误:循环引用导致 infer 失效
type Node = { value: number; next: Node };
type BadGetValue<T> = T extends { value: infer V } ? V : never;
type R = BadGetValue<Node>; // number(看似正确)
// 但深度嵌套时会触发栈溢出
type DeepNode = { value: number; next: { value: string; next: DeepNode } };
type BadDeepValue<T> = T extends { value: infer V; next: infer N } ? V | BadDeepValue<N> : never;
// type R = BadDeepValue<DeepNode>; // 错误:Type instantiation is excessively deep
修复:限制递归深度,或使用 unknown 作为终止条件。
10.3 陷阱三:模板字面量中的空字符串
// 错误:空字符串分隔符导致无限递归
type BadSplit<S extends string, D extends string> =
S extends `${infer H}${D}${infer T}` ? [H, ...BadSplit<T, D>] : [S];
// type R = BadSplit<'abc', ''>; // 错误:无限递归
// 修复:单独处理空字符串
type GoodSplit<S extends string, D extends string> =
D extends ''
? S extends `${infer C}${infer Rest}` ? [C, ...GoodSplit<Rest, D>] : []
: S extends `${infer H}${D}${infer T}` ? [H, ...GoodSplit<T, D>] : [S];
10.4 陷阱四:infer 与重载的顺序
function f(x: string): number;
function f(x: number): string;
function f(x: any): any { return x; }
// 错误:仅使用最后一个签名
type BadReturn = ReturnType<typeof f>; // string(最后一个签名的返回类型)
// 修复:手动提取所有签名
type AllOverloads<T> =
T extends {
(...a: infer A1): infer R1;
(...a: infer A2): infer R2;
} ? R1 | R2 : never;
10.5 陷阱五:infer 变量的子类型收窄
// 错误:infer U 的子类型收窄不正确
type BadF<T> = T extends (x: infer U) => void ? U : never;
type R1 = BadF<(x: string | number) => void>; // string | number(正确)
type R2 = BadF<(x: 'a' | 'b') => void>; // 'a' | 'b'(正确)
// 但当传入具体值时...
type R3 = BadF<(x: 'a') => void>; // 'a'(正确,保留了字面量类型)
// 子类型收窄问题:
type BadFilter<T, U> = T extends (x: infer X) => x is U ? X : never;
// 错误:x is U 中 x 不在作用域内
修复:使用 infer X extends U 显式约束:
type GoodFilter<T, U> = T extends (x: infer X extends U) => x is U ? X : never;
10.6 陷阱六:infer 在 never 上的特殊行为
// 错误:never 在分布式条件类型中被过滤
type BadF<T> = T extends Promise<infer U> ? U : T;
type R1 = BadF<never>; // never(被过滤了,而非返回 never)
// 修复:用 [T] 阻止分布式
type GoodF<T> = [T] extends [Promise<infer U>] ? U : T;
type R2 = GoodF<never>; // never(正确,返回 never 而非被过滤)
10.7 陷阱七:递归深度限制
// 错误:深度递归触发栈溢出
type DeepFlatten<T extends any[]> =
T extends [infer H, ...infer R]
? H extends any[] ? [...DeepFlatten<H>, ...DeepFlatten<R>] : [H, ...DeepFlatten<R>]
: [];
type R = DeepFlatten<[[[[[[[[[[1]]]]]]]]]]>; // 通常可工作
type R2 = DeepFlatten<[[[[[[[[[[[[[[[[[[[[1]]]]]]]]]]]]]]]]]]]]>; // 可能触发深度限制
修复:使用尾递归优化或拆分递归层级。
11. 工程实践
11.1 推荐的 tsconfig 配置
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022", "DOM", "DOM.Iterable"],
// 严格模式——所有 infer 工具类型的基石
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
// 高级类型相关
"noUncheckedIndexedAccess": true, // 使 T[K] 包含 undefined
"exactOptionalPropertyTypes": true,
// 性能优化
"skipLibCheck": true, // 跳过 .d.ts 检查以加速
"incremental": true,
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo",
// 错误提示
"noErrorTruncation": true, // 显示完整错误信息
"pretty": true
}
}
11.2 性能调优
11.2.1 识别性能热点
# 使用 tsc 的扩展诊断
tsc --extendedDiagnostics
# 输出示例:
# Files: 12450
# Lines of Library: 40231
# Lines of TypeScript: 156723
# Identifiers: 890123
// ...
11.2.2 优化策略
- 限制递归深度:所有递归类型工具应有明确的基线情形。
- 使用尾递归:将非尾递归改写为尾递归(TypeScript 4.5+ 优化)。
- 缓存中间结果:用
type Cache<T> = T;缓存复杂的中间类型。 - 拆分大型联合类型:避免 50+ 成员的联合类型,分而治之。
11.2.3 编译时复杂度估算
| 操作 | 复杂度 | 实际耗时(10万行项目) |
|---|---|---|
简单 infer 提取 | < 1ms | |
| 元组递归(深度 < 10) | 1-5ms | |
| 模板字面量递归(长度 < 50) | 5-20ms | |
| 深度递归(深度 = 50) | 50-500ms | |
| 大型联合类型分发(100 成员) | 100ms-1s |
11.3 IDE 集成
11.3.1 VS Code 推荐配置
// .vscode/settings.json
{
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
"typescript.preferences.preferTypeOnlyAutoImports": true,
"typescript.updateImportsOnFileMove.enabled": "always",
"typescript.inlayHints.parameterNames.enabled": "all",
"typescript.inlayHints.variableTypes.enabled": true,
"typescript.inlayHints.propertyDeclarationTypes.enabled": true
}
11.3.2 调试类型推导
// 使用 satisfies 检查类型推导
const config = {
returnType: null as unknown as ReturnType<typeof f>,
} satisfies Record<string, unknown>;
// 使用工具类型显示推导结果
type ShowType<T> = { [K in keyof T]: T[K] };
type Result = ShowType<{ a: ReturnType<typeof f>; b: Parameters<typeof g> }>;
11.4 测试策略
// 使用 tsd 进行类型层测试
import { expectType } from 'tsd';
import { ReturnType, Parameters, Awaited } from './utility-types';
// 测试 ReturnType
expectType<string>(null as unknown as ReturnType<() => string>);
expectType<number>(null as unknown as ReturnType<(x: number) => number>);
expectType<void>(null as unknown as ReturnType<() => void>);
// 测试 Parameters
expectType<[number, string]>(null as unknown as Parameters<(x: number, y: string) => void>);
// 测试 Awaited
expectType<number>(null as unknown as Awaited<Promise<Promise<number>>>);
// 测试边界情形
expectType<never>(null as unknown as ReturnType<number>);
11.5 CI/CD 集成
# .github/workflows/type-check.yml
name: Type Check
on: [push, pull_request]
jobs:
type-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm tsc --noEmit --extendedDiagnostics
- run: pnpm tsd
- name: Check type coverage
run: pnpm type-coverage --detail --strict --atleast 99
12. 案例研究
12.1 案例一:tRPC 的类型安全 RPC
tRPC 是一个端到端类型安全的 RPC 框架,其类型系统的核心是 infer:
// tRPC 服务端定义
import { initTRPC } from '@trpc/server';
import { z } from 'zod';
const t = initTRPC.create();
const appRouter = t.router({
user: t.router({
getById: t.procedure
.input(z.object({ id: z.string() }))
.output(z.object({ id: z.string(), name: z.string() }))
.query(({ input }) => {
return { id: input.id, name: 'Alice' };
}),
}),
});
export type AppRouter = typeof appRouter;
// 客户端使用——类型完全自动推导
import { createTRPCProxyClient } from '@trpc/client';
import type { AppRouter } from './server';
const client = createTRPCProxyClient<AppRouter>({ url: 'http://localhost:3000' });
async function main() {
// 类型安全:参数与返回值都被正确推导
const user = await client.user.getById.query({ id: '123' });
console.log(user.name); // string
// console.log(user.age); // 编译错误
}
tRPC 的类型推导核心:
// 简化的 tRPC 类型推导
type RouterProcedure<T> =
T extends { _input: infer I; _output: infer O }
? { input: I; output: O }
: never;
type RouterClient<T> = {
[K in keyof T]: T[K] extends { _query: infer P }
? { query: (input: RouterProcedure<P>['input']) => Promise<RouterProcedure<P>['output']> }
: T[K] extends { _mutation: infer P }
? { mutate: (input: RouterProcedure<P>['input']) => Promise<RouterProcedure<P>['output']> }
: T[K] extends Record<string, any>
? RouterClient<T[K]>
: never;
};
12.2 案例二:Drizzle ORM 的类型安全查询
import { drizzle } from 'drizzle-orm/pg-core';
import { pgTable, serial, text, integer } from 'drizzle-orm/pg-core';
// Schema 定义
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
age: integer('age').notNull(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
authorId: integer('author_id').references(() => users.id),
});
const db = drizzle({ schema: { users, posts } });
// 类型安全的查询
const result = await db
.select({ id: users.id, name: users.name })
.from(users)
.where(users.age > 18);
// result 的类型自动推导为 { id: number; name: string }[]
Drizzle 的类型推导依赖 infer:
// 简化的 Drizzle 类型推导
type SelectResult<T extends Record<string, AnyColumn>> = {
[K in keyof T]: T[K] extends AnyColumn<infer Type> ? Type : never;
};
type WhereCondition<T> = {
[K in keyof T]: T[K];
};
12.3 案例三:Zod 的类型推导
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
roles: z.array(z.enum(['admin', 'user', 'guest'])),
});
type User = z.infer<typeof UserSchema>;
// 等价于:
// {
// id: number;
// name: string;
// email: string;
// roles: ('admin' | 'user' | 'guest')[];
// }
Zod 的 z.infer 实现简化版:
type infer<T extends ZodType> = T extends ZodType<infer Output, any> ? Output : never;
13. 习题
13.1 填空题
-
(remember) TypeScript 2.8 引入
infer的 PR 编号是 ______。 -
(understand) 条件类型
T extends Promise<infer U> ? U : T中,infer U引入的变量 U 通过 ______ 算法在类型层被求解。 -
(apply) 实现一个
SecondParameter<T>类型,提取函数的第二个参数类型:type SecondParameter<T extends (...args: any) => any> = T extends (first: any, second: infer S, ...rest: any[]) => any ? S : never;中,应填入 ______。 -
(analyze) 在分布式条件类型中,若 T 是
never,则T extends Promise<infer U> ? U : T的结果是 ______,因为 never 在分布式条件下会被 ______。 -
(evaluate) TypeScript 4.5 引入的
Awaited<T>内置类型,其对Promise<Promise<Promise<T>>>的求值结果是 ______。
13.2 选择题
-
(understand) 下列哪种
infer写法是合法的?- A.
type F<T> = infer U extends T ? U : never; - B.
type F<T> = T extends infer U ? U : never; - C.
type F<T> = T extends (x: infer U extends string) => void ? U : never; - D.
type F<T> = T extends Promise<infer U> extends Promise<infer V> ? U : V : never;
答案:C
- A.
-
(analyze) 下列关于
infer在元组中的行为,哪项正确?- A.
infer R在 rest 元素位置总是推断为数组类型 - B.
infer R在 rest 元素位置的推断类型取决于输入元组的形态 - C.
type Tail<T> = T extends [any, ...infer R] ? R : never中,R 一定是any[] - D.
infer不能在元组的 rest 位置使用
答案:B
- A.
-
(evaluate) 下列哪种实现能正确判断类型 T 是否为
any?- A.
type IsAny<T> = T extends any ? true : false; - B.
type IsAny<T> = 0 extends 1 & T ? true : false; - C.
type IsAny<T> = keyof T extends never ? true : false; - D.
type IsAny<T> = T === any ? true : false;
答案:B
- A.
-
(create) 下列哪种方案能正确实现
DeepReadonly且不破坏函数、Date 等类型?- A. 直接递归映射所有属性
- B. 在递归前先用条件类型排除函数、Date、Map、Set 等
- C. 使用
Readonly<T>即可 - D. 用
Object.freeze包装
答案:B
13.3 代码修正题
- (apply) 下列
Awaited实现对Promise<Promise<number>>推导错误,请修复:
type Awaited<T> = T extends Promise<infer U> ? U : T;
// type R = Awaited<Promise<Promise<number>>>; // 期望 number,实际 Promise<number>
修复:
type Awaited<T> = T extends Promise<infer U> ? Awaited<U> : T;
- (analyze) 下列
IsEqual实现在any上有误,请修复:
type IsEqual<A, B> = A extends B ? (B extends A ? true : false) : false;
// type R1 = IsEqual<any, number>; // 期望 false,实际 true
// type R2 = IsEqual<never, never>; // 期望 true,实际 never
修复:
type IsEqual<A, B> =
(<T>() => T extends A ? 1 : 2) extends
(<T>() => T extends B ? 1 : 2) ? true : false;
- (create) 实现一个
Path<T>类型,生成对象 T 的所有点分路径:
// 期望:
type R = Path<{ a: { b: { c: number }; d: string } }>;
// 'a' | 'a.b' | 'a.b.c' | 'a.d'
参考实现:
type Path<T, Prefix extends string = ''> =
T extends object
? {
[K in keyof T & string]:
| `${Prefix}${Prefix extends '' ? '' : '.'}${K}`
| Path<T[K], `${Prefix}${Prefix extends '' ? '' : '.'}${K}`>;
}[keyof T & string]
: never;
13.4 开放性问题
-
(evaluate) 请用 300 字以内论证:为什么 TypeScript 选择
infer+ 合一算法,而不是 Haskell 的类型族?从开放世界假设、结构性类型、子类型、JavaScript 兼容性四个维度展开。 -
(create) 设计一个
TypeRouter<S>类型工具,将 URL 路径模式(如/users/:userId/posts/:postId)映射为对应的请求处理函数类型。要求:路径参数类型安全、HTTP 方法(GET/POST/PUT/DELETE)作为可选约束、支持嵌套路由。给出至少 3 个测试用例。 -
(evaluate) TypeScript 5.4 引入的
NoInfer<T>解决了什么问题?请给出至少 2 个使用NoInfer的实际场景,并解释如果没有它会怎样。 -
(create) 假设你要重新设计 TypeScript 的
infer机制,会如何改进以支持以下能力?- 多个
infer变量之间的依赖关系 infer变量的协变/逆变标注infer在 for-all 量化(而不仅是 exists 量化)下的语义
请给出形式化定义与至少 1 个应用示例。
- 多个
14. 参考文献
[1] Hejlsberg, A. 2018. Conditional Types. Microsoft/TypeScript Pull Request #21496. https://github.com/microsoft/TypeScript/pull/21496
[2] Bierman, G. M., Abadi, M., and Torgersen, M. 2014. Understanding TypeScript. In Proceedings of the 28th European Conference on Object-Oriented Programming (ECOOP’14), 257–281. DOI: 10.1007/978-3-662-44202-9_11
[3] Pierce, B. C. 2002. Types and Programming Languages. MIT Press.
[4] Pierce, B. C. and Turner, D. N. 2000. Local Type Inference. ACM Transactions on Programming Languages and Systems (TOPLAS) 22, 1, 1–44. DOI: 10.1145/345099.345100
[5] Ajvani, B., Vahidi, S., and Itzhaki, S. 2023. Type-level Programming in TypeScript. arXiv preprint arXiv:2302.09465. DOI: 10.48550/arXiv.2302.09465
[6] Hosoya, H. and Pierce, B. C. 2003. Regular Expression Pattern Matching for XML. Journal of Functional Programming 13, 6, 961–1004. DOI: 10.1017/S0956796803000314
[7] Cardelli, L., Martini, S., Mitchell, J. C., and Scedrov, A. 1994. An Extension of System F with Subtyping. Information and Computation 109, 1–2, 4–36. DOI: 10.1006/inco.1994.1093
[8] Microsoft. 2024. TypeScript Handbook: Conditional Types. https://www.typescriptlang.org/docs/handbook/2/conditional-types.html
[9] Microsoft. 2024. TypeScript Handbook: Type Inference. https://www.typescriptlang.org/docs/handbook/type-inference.html
[10] Scherer, G. 2020. Type Inference: What does an inference algorithm infer? SIGPLAN Blogs. https://blog.sigplan.org/2020/04/07/type-inference-what-does-an-inference-algorithm-infer/
[11] ECMA International. 2024. ECMAScript 2024 Language Specification. ECMA-262 15th Edition. https://tc39.es/ecma262/
[12] Robinson, J. A. 1965. A Machine-Oriented Logic Based on the Resolution Principle. Journal of the ACM (JACM) 12, 1, 23–41. DOI: 10.1145/321250.321253
[13] Rémy, D. 1989. Type Inference for Records in a Natural Extension of ML. In Proceedings of the 16th ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL’89), 242–249. DOI: 10.1145/75277.75303
[14] Owens, S., Weirich, D., and Ramsay, F. 2011. A Type System for JavaScript. In Proceedings of the 38th Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (POPL’11), 1–12. DOI: 10.1145/1926385.1926387
15. 延伸阅读
15.1 书籍
- Pierce, B. C. Types and Programming Languages (MIT Press, 2002) — 类型论的经典教材,第 10-11 章详细讲解合一与类型推导。
- Pierce, B. C. Advanced Topics in Types and Programming Languages (MIT Press, 2004) — 第 2 章子类型、第 4 章类型推导。
- Oliveira, B. C. d. S. et al. Scala 3 by Example (Artima, 2023) — Scala 3 的 given/using 机制与 TypeScript
infer的对比。 - Yorgey, B. Typeclassopedia (HaskellWiki, 2018) — Haskell 类型类系统的全面综述。
15.2 论文
- Hejlsberg, A. Conditional Types PR (2018) — TypeScript
infer的原始 PR 讨论。 - Bierman, G. M. et al. Understanding TypeScript (ECOOP 2014) — TypeScript 类型系统的形式化建模。
- Owens, S. et al. A Type System for JavaScript (POPL 2011) — JavaScript 静态类型系统的早期工作。
15.3 开源项目
- type-fest (sindresorhus/type-fest) — TypeScript 类型工具库,包含数百个
infer应用实例。 - ts-toolbelt (devloops/ts-toolbelt) — 类型体操工具库。
- type-challenges (type-challenges/type-challenges) — TypeScript 类型体操题集,包含从入门到进阶的练习。
- effective-typescript (danvk/effective-typescript) — Effective TypeScript 一书的配套代码。
- typepark (piotrwitek/typepark) — 类型体操实验场。
15.4 在线资源
- TypeScript 官方手册 — https://www.typescriptlang.org/docs/handbook/intro.html
- TypeScript 深入教程 — https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html
- TypeScript 严格模式指南 — https://www.typescriptlang.org/tsconfig
- TypeScript 性能调优 — https://github.com/microsoft/TypeScript/wiki/Performance
15.5 视频课程
- MIT 6.826: Principles of Computer Systems — 类型系统与形式化方法。
- Stanford CS143: Compilers — 类型检查与合一算法。
- CMU 15-410: Operating System Design — 系统编程中的类型安全。
- Anders Hejlsberg: TypeScript Design Notes — Channel 9 上的 TypeScript 设计系列访谈。
16. 总结
infer 关键字是 TypeScript 类型系统从”描述型”演进到”计算型”的分水岭。通过本教程的学习,读者应能:
- 理解
infer的形式语义——它是一个存在量化变量,由编译器的合一算法求解。 - 应用
infer解决工程问题——从函数签名提取、Promise 解包到字符串模板解析、对象属性过滤。 - 分析
infer与分布式条件类型、递归类型、模板字面量类型的相互作用,识别推导失败的根本原因。 - 评估 不同
infer写法的工程权衡——编译器性能、类型错误可读性、IDE 提示质量。 - 创造 生产级的类型工具——SQL 解析器、JSON Schema 转换器、类型安全的路由系统。
掌握 infer 是成为 TypeScript 高级工程师的必经之路。它不仅是一个语法特性,更是一种”在类型层思考”的范式。希望本教程能为你打开 TypeScript 类型系统的大门,让你在工程实践中游刃有余地运用 infer 构建类型安全、可维护、可扩展的应用。