infer关键字(TS的灵魂)
# infer 推断「参数 / 返回值」这一件事
# 一、先给你一句「人话版结论」
infer的作用只有一个: 在extends判断成功时,把某一部分类型“抓出来”,起个名字
在函数里,它最常抓的就是:
- 参数列表
- 返回值
# 二、infer 推断【返回值】(最容易)
# 1️⃣ 标准写法(ReturnType 原理)
type MyReturnType<T> =
T extends (...args: any[]) => infer R
? R
: never
2
3
4
# 逐字翻译成中文
如果 T 是函数 就把它的「返回值类型」取出来,叫 R 否则返回 never
# 2️⃣ 用具体例子算一遍
type Fn = (a: number, b: string) => boolean
type R = MyReturnType<Fn>
2
3
# TS 在脑子里做的事:
(a: number, b: string) => boolean
extends (...args: any[]) => infer R
2
👉 匹配成功 👉 推断出:
R = boolean
✅ 最终结果:
type R = boolean
# 三、infer 推断【参数】(稍微难一点)
# 1️⃣ 标准写法(Parameters 原理)
type MyParameters<T> =
T extends (...args: infer P) => any
? P
: never
2
3
4
# 人话版
如果 T 是函数 就把它的「参数列表」取出来,变成一个元组 否则返回 never
# 2️⃣ 具体例子
type Fn = (id: number, name: string) => void
type P = MyParameters<Fn>
2
3
推断过程:
Fn extends (...args: infer P) => any
👉 匹配成功 👉 TS 自动推断:
P = [number, string]
✅ 结果:
type P = [number, string]
# 四、为什么参数是「元组」?
因为函数参数本来就有:
- 顺序
- 个数
- 每一项的类型
(a: number, b: string)
↓
[number, string]
2
3
📌 所以 Parameters 返回的一定是 元组
# 五、把「参数 + 返回值」放一起看(会更清楚)
type Fn = (x: number, y: string) => boolean
type Args = Parameters<Fn> // [number, string]
type Res = ReturnType<Fn> // boolean
2
3
4
你现在已经能完整看懂这两个内置类型的原理了。
# 六、一个真实前端例子(你马上就通)
# 1️⃣ 包一层函数,类型不丢
function withLog<T extends (...args: any[]) => any>(fn: T) {
return (...args: Parameters<T>): ReturnType<T> => {
console.log(args)
return fn(...args)
}
}
2
3
4
5
6
- 📌 重点不是你能写出来
- 📌 是你现在能看懂每一部分在干嘛
# 七、infer 的 3 个铁律(背下来就行)
1️⃣ infer 只能用在 extends 里 2️⃣ infer 只在条件成立时生效 3️⃣ infer 是 TS 自动推断,不是你传的
# 八、极简记忆法 🧠
| 想要什么 | 写法 |
|---|---|
| 返回值 | (...args) => infer R |
| 参数 | (...args: infer P) => any |
# 例子一
# 你看不懂的是这一句 👇
type Params<T> = T extends (...args: infer P) => any ? P : never
我会 拆成 4 步,每一步都“能懂了再往下”。
# 第 1 步:先看你「已经认识的部分」
我们先把最吓人的 infer P 拿掉,看一个你熟的👇
T extends (...args: any[]) => any ? X : Y
这句话的意思是:
如果 T 是一个函数类型
- 是 → 返回
X- 否 → 返回
Y
例如:
type A = string extends Function ? 1 : 2 // 2
type B = (() => void) extends Function ? 1 : 2 // 1
2
到这里为止,完全不神秘,对吧?
# 第 2 步:(...args: any[]) => any 是什么?
这是一个函数类型:
(...args: any[]) => any
意思是:
任意参数、任意返回值的函数
所以:
T extends (...args: any[]) => any
等价于问一句人话:
T 是不是一个函数?
# 第 3 步:infer P 到底是干嘛的(重点)
现在我们看核心👇
(...args: infer P) => any
# 这句话的意思是:
如果 T 是函数,那就把它的「参数列表」抓出来, 存到一个变量 P 里
⚠️ 注意:
P不是你传的- 是 TS 自动帮你推断出来的
# 用一个非常具体的例子
type Fn = (a: number, b: string) => void
我们代入:
Fn extends (...args: infer P) => any ? P : never
TS 在做什么?
👉 它在问:
这个
Fn能不能匹配这种函数结构?
(...args: ???) => any
答案是:能
于是 TS 自动推断:
P = [number, string]
# 第 4 步:整个类型一起算一遍(完整流程)
type Params<T> =
T extends (...args: infer P) => any
? P
: never
2
3
4
# 情况 1:T 是函数
type A = Params<(x: number, y: string) => void>
计算过程:
(x: number, y: string) => void
extends (...args: infer P) => any
2
👉 匹配成功
👉 P = [number, string]
结果:
type A = [number, string]
# 情况 2:T 不是函数
type B = Params<string>
计算:
string extends (...args: infer P) => any ❌
👉 走 : never
结果:
type B = never
# 一句话版本(请记这个)
infer = “把某一部分类型,起个变量名存起来”
在这里:
infer P
就是:
把函数的参数列表存到 P 里
# 用一句大白话翻译整行代码
type Params<T> = T extends (...args: infer P) => any ? P : never
⬇️
如果 T 是函数,就返回它的参数列表; 如果不是函数,就返回 never
# 为什么这东西这么重要?
因为下面这些你以后天天用的,本质都是它:
Parameters<T>
ReturnType<T>
ConstructorParameters<T>
2
3
例如:
type P = Parameters<(a: number, b: string) => void>
// [number, string]
2
# 例子二
# 你不懂的是这一句 👇
type Awaited<T> = T extends Promise<infer R> ? Awaited<R> : T
我会 拆成 5 个极小步骤,每一步都很“傻”,但一定能懂。
# 第 1 步:先看“最外层结构”(你其实已经见过)
T extends XXX ? A : B
这是 类型版 if 判断:
如果 T 符合 XXX → 用 A 否则 → 用 B
到这里 没有新东西。
# 第 2 步:Promise<infer R> 是在干嘛?
先忘掉 infer,只看:
Promise<something>
你在 JS 里很熟:
Promise<number>
Promise<string>
2
现在这句:
T extends Promise<infer R>
翻成人话是:
T 是不是一个 Promise? 如果是,把 Promise 里包的东西拿出来
# 第 3 步:infer R = “自动拆包”
这是关键,但我们说一句人话就够了:
infer R = “如果能匹配成功,就把里面的类型取出来,叫它 R”
# 举一个你 100% 熟的例子
type T = Promise<number>
代入:
Promise<number> extends Promise<infer R>
TS 会说:
👉 嗯,结构一样 👉 那我推断出:
R = number
# 第 4 步:现在看 ? Awaited<R> : T
我们已经知道:
- 如果 T 是 Promise → R 是里面的值
- 如果不是 Promise → 直接返回 T
但这里不是返回 R,而是:
Awaited<R>
⚠️ 重点来了:
它又把 R 再丢回 Awaited 里算一次
这一步叫:递归
# 第 5 步:完整走一遍(最重要)
# 🌰 示例 1:普通 Promise
type A = Awaited<Promise<number>>
计算过程:
# 第一次
T = Promise<number>
✔ 是 Promise
👉 R = number
👉 结果 = Awaited<number>
# 第二次
T = number
❌ 不是 Promise
👉 走 : T
结果 = number
✅ 最终结果:number
# 🌰 示例 2:Promise 套 Promise(这就是它存在的意义)
type B = Awaited<Promise<Promise<string>>>
# 第一次
T = Promise<Promise<string>>
R = Promise<string>
→ Awaited<Promise<string>>
2
3
# 第二次
T = Promise<string>
R = string
→ Awaited<string>
2
3
# 第三次
T = string
❌ 不是 Promise
→ string
2
3
✅ 最终结果:string
# 现在用一句大白话翻译整句代码
type Awaited<T> =
T extends Promise<infer R>
? Awaited<R>
: T
2
3
4
⬇️
如果 T 是 Promise,就把它拆开 如果拆开后里面还是 Promise,就继续拆 直到拆到不是 Promise 为止 然后返回那个值
# 和 await 的关系(你马上就通)
JS 里:
const res = await await await promise
TS 里:
type Res = Awaited<typeof promise>
👉 一模一样的思想
# 你现在只需要记住这 3 句话(真的)
- 1️⃣
infer= 帮我把里面的类型取出来 - 2️⃣
Awaited<R>= 继续拆,直到不能拆 - 3️⃣ 整个类型 = 模拟 JS 的 await 行为