在 TypeScript 的高级玩法里,infer 经常让初学者感到头大。它长得像关键字,用起来像正则表达式的"捕获组",还必须寄生在 extends 条件语句里。
要把这东西彻底搞清楚,我们得先拆解它的核心逻辑,再看看它在实战中到底解决了什么问题。
一、 核心概念:infer 到底是什么?
简单来说,infer 就是 "类型系统里的临时变量" 。
在常规的泛型中,是你告诉 TypeScript 具体的类型;而在使用 infer 的场景下,是 TypeScript 自动推断出某个位置的类型,并把它存到一个变量里供你后续使用。
语法规则:
- 只能在
extends条件类型的"真"分支中使用。 - 配合模式匹配使用。 你给出一个"模版"(比如函数结构、数组结构),让 TS 去匹配并提取其中的零件。
二、 语义纠偏:extends 的"变脸"
很多人的困惑源于 extends 这个词。在 Class 里它是"继承",但在类型定义(尤其是配合 infer)时,它其实是 "模式匹配(Pattern Matching)" 。
- Class 中的
extends:我是你的后代,我继承你的基因。 - 类型中的
extends:我能不能塞进你这个形状的盒子里?
当你在写 T extends (infer R)[] ? R : never 时,你实际上是在对 TS 说:
"帮我看看
T是不是一个数组。如果是,顺便把数组里装的那个东西的类型抠出来,起个临时名字叫R。如果匹配成功,我就要这个R。"
三、 实战场景:它能解决什么痛苦?
如果没有 infer,类型系统就是静态的、死板的。有了它,类型系统就具备了"解剖"和"重组"的能力。
1. 经典的"解包" (Unpacking)
这是最常见的用途。比如从 Promise、Array 或 Map 中提取内部类型。
TypeScript
// 提取 Promise 内部的类型
type Unbox<T> = T extends Promise<infer U> ? U : T;
type Str = Unbox<Promise<string>>; // 得到 string
2. 函数全家桶 (Function Extraction)
你可以轻松拿到一个函数的返回类型、参数类型,甚至是构造函数的参数。
TypeScript
// 提取函数第一个参数的类型
type FirstParam<T> = T extends (arg1: infer P, ...args: any[]) => any ? P : never;
function saveUser(id: number, name: string) {}
type IDType = FirstParam<typeof saveUser>; // number
3. 字符串模板的"手术刀"
这是 TS 4.1 之后的黑科技。你可以用它来拆分字符串,做一些像"驼峰转下划线"之类的类型转换。
TypeScript
type GetExtension<T> = T extends `${string}.${infer Ext}` ? Ext : never;
type FileExt = GetExtension<"config.json">; // "json"
四、 总结:什么时候该用它?
你不需要在每一处代码都写 infer,但在以下场景,它是无可替代的神器:
- 处理第三方库 :当你拿不到某个库内部定义的具体接口,但你能拿到它的函数或实例时,可以用
infer反向推导出它的类型。 - 减少重复定义 :不想为了一个返回值再去手动写一遍复杂的
interface。 - 编写通用工具库:它是构建自动化、高适配性类型系统的基石。
虽然 infer 很好用,但它会显著增加类型的理解成本。对于团队协作项目,建议只在底层工具类型(Utils)中使用它,业务代码中还是尽量保持类型声明的直观和显式。