前置知识: TypeScript

类型推断infer扩展

42 minAdvanced2026/7/20

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)体系的基石——ReturnTypeParametersAwaitedConstructorParameters 等内置工具类型无一不是 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 infer keyword.”

设计灵感来自三个来源:

  1. Haskell 的类型族(type families):但 Haskell 的类型族是闭合的、需要显式声明,与 TypeScript 的开放世界不兼容。
  2. Scala 的抽象类型成员(abstract type members):Scala 允许在 trait 中声明 type T,由具体实现填充;TypeScript 选择更激进的方案——让编译器自动填充。
  3. C++ 的模板参数推导:C++17 的 template <class T> void f(T)T 由编译器推导,TypeScript 借鉴了”编译器推导类型变量”的思想。

1.2 时间线:从 2.8 到 5.x

下表列出了 infer 关键能力在 TypeScript 各版本中的演进:

版本年份关键能力PR / Issue
2.82018-03infer 关键字在条件类型中引入#21496
3.02018-07元组中的 rest 元素支持 infer#24897
3.42019-03infer T extends X 约束推断变量#25260
4.02020-08元组与 rest 元素的 infer 推导更精确#39094
4.12020-11模板字面量类型支持 infer#40336
4.52021-11内置 Awaited<T> 类型#46052
4.72022-05infer extends 约束语法稳定化#49385
5.02023-03const 类型参数与 infer 协同#51865
5.42024-03NoInfer<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 类型论形式语义

Γ\Gamma 为类型环境,\vdash 为类型判断关系,TypeScript 的条件类型推导规则可形式化为:

ΓT:ΓU:unify(T,U[α/X])=σΓT extends U[α] ? X:YσX\frac{\Gamma \vdash T : \star \quad \Gamma \vdash U : \star \quad \text{unify}(T, U[\alpha / X]) = \sigma}{\Gamma \vdash T \text{ extends } U[\alpha] \text{ ? } X : Y \rightsquigarrow_\sigma X}

读作:若在类型环境 Γ\GammaTTUU 是良构类型,且合一算法能找到替换 σ\sigma 使 TσUT \equiv \sigma U,则条件类型的结果为 σX\sigma X;否则为 YY。其中 α\alphainfer 引入的存在量化变量,XXYY 是条件类型的两个分支。

2.3 合一算法简介

TypeScript 编译器使用的合一算法是 Robinson 合一的变体,其核心思想是:

  1. 将待匹配的两个类型分别记为 TTUU,其中 UU 含有存在变量 α1,,αn\alpha_1, \dots, \alpha_n
  2. 递归地比较 TTUU 的结构:
    • T=UT = U(结构相同),返回空替换 \emptyset
    • UUαi\alpha_i,将 αi\alpha_i 绑定到 TT 对应位置的子类型。
    • TTUU 的构造器不同(如 Promise vs Array),失败。
  3. 合一过程可能产生多重替换,需要进行组合与一致性检查。

形式化地,合一函数 unify:Type×TypeSubst{}\text{unify} : \text{Type} \times \text{Type} \to \text{Subst} \cup \{\bot\} 满足:

unify(T,U)=σ    σT=σU\text{unify}(T, U) = \sigma \iff \sigma T = \sigma U

其中 σ\sigma 是最一般统一元(Most General Unifier, MGU)。

2.4 与 System F 的关系

TypeScript 的类型系统可视为 System F<:sub>(带子类型的 System F)的扩展。infer 引入的存在量化变量对应 System F 中的存在类型 α.T\exists \alpha. T,但 TypeScript 不要求显式打包(pack)与解包(unpack),而是由编译器在条件类型求值时自动完成。

形式化对比:

概念System F<:subTypeScript
类型抽象Λα.t\Lambda \alpha. t<T>(t: T) => ...
类型应用t[τ]t[\tau]f<T>(...)
存在类型α.T\exists \alpha. TT extends { x: infer U } ? ...
合一手动 pack/unpack编译器自动

3. 理论推导

3.1 类型推导规则

下面给出 infer 的核心推导规则,使用类型论中常见的推断横线(inference rule)表示法:

规则 R1:基本提取

ΓT:Promiseτα freshΓT extends Promiseinfer α ? α:neverτ\frac{\Gamma \vdash T : \text{Promise}\langle\tau\rangle \quad \alpha \text{ fresh}}{\Gamma \vdash T \text{ extends Promise}\langle\text{infer } \alpha\rangle \text{ ? } \alpha : \text{never} \rightsquigarrow \tau}

读作:若 TTPromise<τ>,则 T extends Promise<infer α> ? α : never 求值为 τ

规则 R2:失败分支

ΓT:τ1τ1≢PromiseΓT extends Promiseinfer α ? α:nevernever\frac{\Gamma \vdash T : \tau_1 \quad \tau_1 \not\equiv \text{Promise}\langle\cdot\rangle}{\Gamma \vdash T \text{ extends Promise}\langle\text{infer } \alpha\rangle \text{ ? } \alpha : \text{never} \rightsquigarrow \text{never}}

读作:若 TT 不是 Promise,则条件类型走 else 分支,结果为 never

规则 R3:分布式分发

ΓT:τ1τ2τ1,τ2 not neverΓT extends Promiseinfer α ? α:neverF(τ1)F(τ2)\frac{\Gamma \vdash T : \tau_1 \mid \tau_2 \quad \tau_1, \tau_2 \text{ not never}}{\Gamma \vdash T \text{ extends Promise}\langle\text{infer } \alpha\rangle \text{ ? } \alpha : \text{never} \rightsquigarrow F(\tau_1) \mid F(\tau_2)}

其中 F(τ)=τ extends Promiseinfer α ? α:neverF(\tau) = \tau \text{ extends Promise}\langle\text{infer } \alpha\rangle \text{ ? } \alpha : \text{never}。这是分布式条件类型的关键规则:当输入是裸联合类型时,对每个成员单独应用规则。

规则 R4:递归推导

ΓT:PromisePromiseτα1,α2 freshΓAwaitedTAwaitedτ\frac{\Gamma \vdash T : \text{Promise}\langle\text{Promise}\langle\tau\rangle\rangle \quad \alpha_1, \alpha_2 \text{ fresh}}{\Gamma \vdash \text{Awaited}\langle T \rangle \rightsquigarrow \text{Awaited}\langle\tau\rangle}

读作: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

约束的语义可形式化为:

ΓT:τunify(τ,U[α/C])=σσα<:CΓT extends U[α extends C] ? α:Yσα\frac{\Gamma \vdash T : \tau \quad \text{unify}(\tau, U[\alpha/C]) = \sigma \quad \sigma \alpha <: C}{\Gamma \vdash T \text{ extends } U[\alpha \text{ extends } C] \text{ ? } \alpha : Y \rightsquigarrow \sigma \alpha}

其中 CC 是约束类型,σα<:C\sigma \alpha <: C 表示求解后的 α\alpha 必须是 CC 的子类型。

3.3 复杂度分析

infer 的合一算法在最坏情况下的复杂度为 O(n2)O(n^2),其中 nn 是类型表达式的大小。对于深度嵌套的递归类型(如 DeepReadonly),实际复杂度可达 O(2d)O(2^d),其中 dd 是递归深度。

TypeScript 4.5 起对尾递归条件类型做了优化,将原本的 O(d)O(d) 栈空间复杂度降为 O(1)O(1)。但非尾递归的深度仍受 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 算法的原因:

  1. 结构性类型:HM 假设名义类型,TypeScript 是结构性的。
  2. 子类型:HM 不支持子类型,TypeScript 有复杂的子类型关系。
  3. 重载:JavaScript 函数经常重载,HM 不支持。
  4. 动态特性:JavaScript 的 anyunknownnever 等类型在 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 起,autodecltype 提供了更强的推导能力:

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 优化策略

  1. 限制递归深度:所有递归类型工具应有明确的基线情形。
  2. 使用尾递归:将非尾递归改写为尾递归(TypeScript 4.5+ 优化)。
  3. 缓存中间结果:用 type Cache<T> = T; 缓存复杂的中间类型。
  4. 拆分大型联合类型:避免 50+ 成员的联合类型,分而治之。

11.2.3 编译时复杂度估算

操作复杂度实际耗时(10万行项目)
简单 infer 提取O(n)O(n)< 1ms
元组递归(深度 < 10)O(d)O(d)1-5ms
模板字面量递归(长度 < 50)O(n)O(n)5-20ms
深度递归(深度 = 50)O(2d)O(2^d)50-500ms
大型联合类型分发(100 成员)O(n2)O(n^2)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 填空题

  1. (remember) TypeScript 2.8 引入 infer 的 PR 编号是 ______。

  2. (understand) 条件类型 T extends Promise<infer U> ? U : T 中,infer U 引入的变量 U 通过 ______ 算法在类型层被求解。

  3. (apply) 实现一个 SecondParameter<T> 类型,提取函数的第二个参数类型:type SecondParameter<T extends (...args: any) => any> = T extends (first: any, second: infer S, ...rest: any[]) => any ? S : never; 中,应填入 ______。

  4. (analyze) 在分布式条件类型中,若 T 是 never,则 T extends Promise<infer U> ? U : T 的结果是 ______,因为 never 在分布式条件下会被 ______。

  5. (evaluate) TypeScript 4.5 引入的 Awaited<T> 内置类型,其对 Promise<Promise<Promise<T>>> 的求值结果是 ______。

13.2 选择题

  1. (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

  2. (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

  3. (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

  4. (create) 下列哪种方案能正确实现 DeepReadonly 且不破坏函数、Date 等类型?

    • A. 直接递归映射所有属性
    • B. 在递归前先用条件类型排除函数、Date、Map、Set 等
    • C. 使用 Readonly<T> 即可
    • D. 用 Object.freeze 包装

    答案:B

13.3 代码修正题

  1. (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;
  1. (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;
  1. (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 开放性问题

  1. (evaluate) 请用 300 字以内论证:为什么 TypeScript 选择 infer + 合一算法,而不是 Haskell 的类型族?从开放世界假设、结构性类型、子类型、JavaScript 兼容性四个维度展开。

  2. (create) 设计一个 TypeRouter<S> 类型工具,将 URL 路径模式(如 /users/:userId/posts/:postId)映射为对应的请求处理函数类型。要求:路径参数类型安全、HTTP 方法(GET/POST/PUT/DELETE)作为可选约束、支持嵌套路由。给出至少 3 个测试用例。

  3. (evaluate) TypeScript 5.4 引入的 NoInfer<T> 解决了什么问题?请给出至少 2 个使用 NoInfer 的实际场景,并解释如果没有它会怎样。

  4. (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 书籍

  1. Pierce, B. C. Types and Programming Languages (MIT Press, 2002) — 类型论的经典教材,第 10-11 章详细讲解合一与类型推导。
  2. Pierce, B. C. Advanced Topics in Types and Programming Languages (MIT Press, 2004) — 第 2 章子类型、第 4 章类型推导。
  3. Oliveira, B. C. d. S. et al. Scala 3 by Example (Artima, 2023) — Scala 3 的 given/using 机制与 TypeScript infer 的对比。
  4. Yorgey, B. Typeclassopedia (HaskellWiki, 2018) — Haskell 类型类系统的全面综述。

15.2 论文

  1. Hejlsberg, A. Conditional Types PR (2018) — TypeScript infer 的原始 PR 讨论。
  2. Bierman, G. M. et al. Understanding TypeScript (ECOOP 2014) — TypeScript 类型系统的形式化建模。
  3. Owens, S. et al. A Type System for JavaScript (POPL 2011) — JavaScript 静态类型系统的早期工作。

15.3 开源项目

  1. type-fest (sindresorhus/type-fest) — TypeScript 类型工具库,包含数百个 infer 应用实例。
  2. ts-toolbelt (devloops/ts-toolbelt) — 类型体操工具库。
  3. type-challenges (type-challenges/type-challenges) — TypeScript 类型体操题集,包含从入门到进阶的练习。
  4. effective-typescript (danvk/effective-typescript) — Effective TypeScript 一书的配套代码。
  5. typepark (piotrwitek/typepark) — 类型体操实验场。

15.4 在线资源

  1. TypeScript 官方手册https://www.typescriptlang.org/docs/handbook/intro.html
  2. TypeScript 深入教程https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html
  3. TypeScript 严格模式指南https://www.typescriptlang.org/tsconfig
  4. TypeScript 性能调优https://github.com/microsoft/TypeScript/wiki/Performance

15.5 视频课程

  1. MIT 6.826: Principles of Computer Systems — 类型系统与形式化方法。
  2. Stanford CS143: Compilers — 类型检查与合一算法。
  3. CMU 15-410: Operating System Design — 系统编程中的类型安全。
  4. Anders Hejlsberg: TypeScript Design Notes — Channel 9 上的 TypeScript 设计系列访谈。

16. 总结

infer 关键字是 TypeScript 类型系统从”描述型”演进到”计算型”的分水岭。通过本教程的学习,读者应能:

  1. 理解 infer 的形式语义——它是一个存在量化变量,由编译器的合一算法求解。
  2. 应用 infer 解决工程问题——从函数签名提取、Promise 解包到字符串模板解析、对象属性过滤。
  3. 分析 infer 与分布式条件类型、递归类型、模板字面量类型的相互作用,识别推导失败的根本原因。
  4. 评估 不同 infer 写法的工程权衡——编译器性能、类型错误可读性、IDE 提示质量。
  5. 创造 生产级的类型工具——SQL 解析器、JSON Schema 转换器、类型安全的路由系统。

掌握 infer 是成为 TypeScript 高级工程师的必经之路。它不仅是一个语法特性,更是一种”在类型层思考”的范式。希望本教程能为你打开 TypeScript 类型系统的大门,让你在工程实践中游刃有余地运用 infer 构建类型安全、可维护、可扩展的应用。

返回入门指南