infer 关键字深入深度解析
infer 等于类型世界的'解构赋值',在条件类型的匹配模式中声明类型变量,由 TS 自动把匹配到的类型片段填进去。 提取函数返回值、解构 Promise、拆数组元组,全是一行代码的事。
infer 关键字深入深度解析
一句话概括
infer = 类型世界的”解构赋值”——在条件类型的匹配模式中声明一个类型变量,TS 自动把匹配到的类型片段填进去。提取函数返回值、解构 Promise、拆数组元组,全是一行代码的事。
核心知识点
1. infer 基础:只能出现在 extends 的真分支
1
2
3
4
5
6
7
8
9
10
11
// 语法:T extends 模式 ? infer R 在 true 分支中使用 : else
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function greet(name: string): string {
return `Hello, ${name}`;
}
type R = MyReturnType<typeof greet>; // string ✅
// infer 的位置 = 你想捕获的类型片段的位置
// 放在返回值位置 → 捕获返回值类型
// 放在参数位置 → 捕获参数元组
2. 提取函数参数(多参数、首参数、尾参数)
1
2
3
4
5
6
7
8
9
10
11
12
13
// 提取全部参数为元组
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
function fn(a: string, b: number, c: boolean): void {}
type AllParams = MyParameters<typeof fn>; // [string, number, boolean]
// 只取第一个参数
type FirstArg<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
type F = FirstArg<typeof fn>; // string
// 只取最后一个参数
type LastArg<T> = T extends (...args: [...any[], infer L]) => any ? L : never;
type L = LastArg<typeof fn>; // boolean
3. infer 解构数组和元组
1
2
3
4
5
6
7
8
9
10
// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;
type E = ElementOf<string[]>; // string
// 提取元组第二个元素(面试常见题)
type Second<T extends any[]> = T extends [any, infer S, ...any[]] ? S : never;
type S = Second<[string, number, boolean]>; // number
// 提取元组最后一项
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;
4. 递归 infer:手写 Awaited
1
2
3
4
5
6
7
8
9
// 深度解包 Promise —— 类型体操入门必写
type MyAwaited<T> =
T extends Promise<infer V>
? MyAwaited<V>
: T;
type T1 = MyAwaited<Promise<string>>; // string
type T2 = MyAwaited<Promise<Promise<number>>>; // number(递归展开)
type T3 = MyAwaited<string>; // string(不是 Promise,原样返回)
5. infer + 模板字面量 = 字符串模式匹配
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 提取路由路径中的参数段
type PathSegments<T extends string> =
T extends `/${infer Seg}/${infer Rest}`
? Seg | PathSegments<`/${Rest}`>
: T extends `/${infer Last}`
? Last
: never;
type Segs = PathSegments<"/api/v2/users">; // "api" | "v2" | "users"
// 提取域名
type Host<T extends string> =
T extends `${string}://${infer H}/${string}` ? H : never;
type H = Host<"https://api.github.com/v3/repos">; // "api.github.com"
6. infer + extends 约束(TS 4.7+)
1
2
3
4
5
6
// infer 后直接加约束:提取模板字符串中的数字并转为 number 类型
type ParseInt<T extends string> =
T extends `${infer N extends number}` ? N : never;
type P1 = ParseInt<"123">; // 123(number 类型,不是字符串)
type P2 = ParseInt<"abc">; // never(不满足 extends number)
其实你每天都在用
ReturnType<typeof someFn>:第三方库没导出函数返回值类型时自己提取Parameters<typeof onClick>[0]:封装高阶组件时提取事件处理函数的参数类型Awaited<ReturnType<typeof fetch>>:去掉 async 函数返回的 Promise 层- Vue
defineEmits类型推导:{ submit: [data: FormData] }内部用 infer 提取参数元组 useReducer的类型推断:从 reducer 函数自动推导 state 和 action 类型
常见误解(FAQ)
❌ 误区 1:「infer 可以在任意 type 定义中使用」
只能出现在条件类型的 extends 子句的真分支(? 之后)。type X = infer R 语法错误。它是条件类型的专属语法糖。
❌ 误区 2:「联合类型 + infer 一定触发分发」
只有裸类型参数才触发分发。[T] extends [infer U] 包了元组,不触发分发。T extends Promise<infer V> 即使传入联合类型,分发的是 T 而不是 V——这经常是面试的扣分点。
❌ 误区 3:「多个 infer 变量在同一个条件类型中会冲突」
不会冲突,但要注意作用域——每个 infer 变量只在当前 ? 分支可用。嵌套条件类型中,内层 infer 会覆盖外层同名变量,像 JS 的变量遮蔽。
❌ 误区 4:「ReturnType<never> 返回什么」
never extends (...args: any[]) => infer R —— 由于 never 是空联合,分发后没有成员,整个表达式结果为 never。这不是 bug,是分发条件类型的特性。
一句话总结
把 infer 当类型的”捕获组”——在 extends 的匹配模式里挖个坑起个名,TS 自动把匹配到的部分填进去,然后在 ? 分支里随便用。配合递归就能做任意深度的类型变换。
本文由作者按照 CC BY 4.0 进行授权