Glittering's blog Glittering's blog
Home
  • 学习手册

    • 《JavaScript教程》
    • 《TypeScript教程》
    • 《Git》
    • 《Vite》
    • 《Vue3》
    • 《React18》
    • 《CSS》
    • 《Tailwind CSS》
    • 《ES6 教程》
    • 《TypeScript 从零实现 axios》
  • 技术文档
  • 算法
  • 工作总结
  • 实用技巧
  • collect
About
  • Classification
  • Label
GitHub (opens new window)

Glitz Ma

前端开发工程师
Home
  • 学习手册

    • 《JavaScript教程》
    • 《TypeScript教程》
    • 《Git》
    • 《Vite》
    • 《Vue3》
    • 《React18》
    • 《CSS》
    • 《Tailwind CSS》
    • 《ES6 教程》
    • 《TypeScript 从零实现 axios》
  • 技术文档
  • 算法
  • 工作总结
  • 实用技巧
  • collect
About
  • Classification
  • Label
GitHub (opens new window)
  • 数据类型

  • 实践应用

    • keyof和typeof
    • 映射类型
    • extends的约束、条件、分发
    • infer关键字(TS的灵魂)
      • infer 推断「参数 / 返回值」这一件事
        • 一、先给你一句「人话版结论」
        • 二、infer 推断【返回值】(最容易)
        • 1️⃣ 标准写法(ReturnType 原理)
        • 逐字翻译成中文
        • 2️⃣ 用具体例子算一遍
        • TS 在脑子里做的事:
        • 三、infer 推断【参数】(稍微难一点)
        • 1️⃣ 标准写法(Parameters 原理)
        • 人话版
        • 2️⃣ 具体例子
        • 四、为什么参数是「元组」?
        • 五、把「参数 + 返回值」放一起看(会更清楚)
        • 六、一个真实前端例子(你马上就通)
        • 1️⃣ 包一层函数,类型不丢
        • 七、infer 的 3 个铁律(背下来就行)
        • 八、极简记忆法 🧠
      • 例子一
        • 你看不懂的是这一句 👇
        • 第 1 步:先看你「已经认识的部分」
        • 第 2 步:(...args: any[]) => any 是什么?
        • 第 3 步:infer P 到底是干嘛的(重点)
        • 这句话的意思是:
        • 用一个非常具体的例子
        • 第 4 步:整个类型一起算一遍(完整流程)
        • 情况 1:T 是函数
        • 情况 2:T 不是函数
        • 一句话版本(请记这个)
        • 用一句大白话翻译整行代码
        • 为什么这东西这么重要?
      • 例子二
        • 你不懂的是这一句 👇
        • 第 1 步:先看“最外层结构”(你其实已经见过)
        • 第 2 步:Promise<infer R> 是在干嘛?
        • 第 3 步:infer R = “自动拆包”
        • 举一个你 100% 熟的例子
        • 第 4 步:现在看 ? Awaited : T
        • 第 5 步:完整走一遍(最重要)
        • 🌰 示例 1:普通 Promise
        • 第一次
        • 第二次
        • 🌰 示例 2:Promise 套 Promise(这就是它存在的意义)
        • 第一次
        • 第二次
        • 第三次
        • 现在用一句大白话翻译整句代码
        • 和 await 的关系(你马上就通)
        • 你现在只需要记住这 3 句话(真的)
  • 《TypeScript》学习笔记
  • 实践应用
mamingjuan
2020-12-07
目录

infer关键字(TS的灵魂)

# infer 推断「参数 / 返回值」这一件事

# 一、先给你一句「人话版结论」

infer 的作用只有一个: 在 extends 判断成功时,把某一部分类型“抓出来”,起个名字

在函数里,它最常抓的就是:

  • 参数列表
  • 返回值

# 二、infer 推断【返回值】(最容易)

# 1️⃣ 标准写法(ReturnType 原理)

type MyReturnType<T> =
  T extends (...args: any[]) => infer R
    ? R
    : never
1
2
3
4
# 逐字翻译成中文

如果 T 是函数 就把它的「返回值类型」取出来,叫 R 否则返回 never


# 2️⃣ 用具体例子算一遍

type Fn = (a: number, b: string) => boolean

type R = MyReturnType<Fn>
1
2
3
# TS 在脑子里做的事:
(a: number, b: string) => boolean
extends (...args: any[]) => infer R
1
2

👉 匹配成功 👉 推断出:

R = boolean
1

✅ 最终结果:

type R = boolean
1

# 三、infer 推断【参数】(稍微难一点)

# 1️⃣ 标准写法(Parameters 原理)

type MyParameters<T> =
  T extends (...args: infer P) => any
    ? P
    : never
1
2
3
4
# 人话版

如果 T 是函数 就把它的「参数列表」取出来,变成一个元组 否则返回 never


# 2️⃣ 具体例子

type Fn = (id: number, name: string) => void

type P = MyParameters<Fn>
1
2
3

推断过程:

Fn extends (...args: infer P) => any
1

👉 匹配成功 👉 TS 自动推断:

P = [number, string]
1

✅ 结果:

type P = [number, string]
1

# 四、为什么参数是「元组」?

因为函数参数本来就有:

  • 顺序
  • 个数
  • 每一项的类型
(a: number, b: string)
↓
[number, string]
1
2
3

📌 所以 Parameters 返回的一定是 元组


# 五、把「参数 + 返回值」放一起看(会更清楚)

type Fn = (x: number, y: string) => boolean

type Args = Parameters<Fn>   // [number, string]
type Res  = ReturnType<Fn>   // boolean
1
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)
  }
}
1
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
1

我会 拆成 4 步,每一步都“能懂了再往下”。


# 第 1 步:先看你「已经认识的部分」

我们先把最吓人的 infer P 拿掉,看一个你熟的👇

T extends (...args: any[]) => any ? X : Y
1

这句话的意思是:

如果 T 是一个函数类型

  • 是 → 返回 X
  • 否 → 返回 Y

例如:

type A = string extends Function ? 1 : 2 // 2
type B = (() => void) extends Function ? 1 : 2 // 1
1
2

到这里为止,完全不神秘,对吧?


# 第 2 步:(...args: any[]) => any 是什么?

这是一个函数类型:

(...args: any[]) => any
1

意思是:

任意参数、任意返回值的函数

所以:

T extends (...args: any[]) => any
1

等价于问一句人话:

T 是不是一个函数?


# 第 3 步:infer P 到底是干嘛的(重点)

现在我们看核心👇

(...args: infer P) => any
1
# 这句话的意思是:

如果 T 是函数,那就把它的「参数列表」抓出来, 存到一个变量 P 里

⚠️ 注意:

  • P 不是你传的
  • 是 TS 自动帮你推断出来的

# 用一个非常具体的例子

type Fn = (a: number, b: string) => void
1

我们代入:

Fn extends (...args: infer P) => any ? P : never
1

TS 在做什么?

👉 它在问:

这个 Fn 能不能匹配这种函数结构?

(...args: ???) => any
1

答案是:能

于是 TS 自动推断:

P = [number, string]
1

# 第 4 步:整个类型一起算一遍(完整流程)

type Params<T> =
  T extends (...args: infer P) => any
    ? P
    : never
1
2
3
4
# 情况 1:T 是函数
type A = Params<(x: number, y: string) => void>
1

计算过程:

(x: number, y: string) => void
extends (...args: infer P) => any
1
2

👉 匹配成功 👉 P = [number, string]

结果:

type A = [number, string]
1

# 情况 2:T 不是函数
type B = Params<string>
1

计算:

string extends (...args: infer P) => any ❌
1

👉 走 : never

结果:

type B = never
1

# 一句话版本(请记这个)

infer = “把某一部分类型,起个变量名存起来”

在这里:

infer P
1

就是:

把函数的参数列表存到 P 里


# 用一句大白话翻译整行代码

type Params<T> = T extends (...args: infer P) => any ? P : never
1

⬇️

如果 T 是函数,就返回它的参数列表; 如果不是函数,就返回 never


# 为什么这东西这么重要?

因为下面这些你以后天天用的,本质都是它:

Parameters<T>
ReturnType<T>
ConstructorParameters<T>
1
2
3

例如:

type P = Parameters<(a: number, b: string) => void>
// [number, string]
1
2

# 例子二

# 你不懂的是这一句 👇

type Awaited<T> = T extends Promise<infer R> ? Awaited<R> : T
1

我会 拆成 5 个极小步骤,每一步都很“傻”,但一定能懂。


# 第 1 步:先看“最外层结构”(你其实已经见过)

T extends XXX ? A : B
1

这是 类型版 if 判断:

如果 T 符合 XXX → 用 A 否则 → 用 B

到这里 没有新东西。


# 第 2 步:Promise<infer R> 是在干嘛?

先忘掉 infer,只看:

Promise<something>
1

你在 JS 里很熟:

Promise<number>
Promise<string>
1
2

现在这句:

T extends Promise<infer R>
1

翻成人话是:

T 是不是一个 Promise? 如果是,把 Promise 里包的东西拿出来


# 第 3 步:infer R = “自动拆包”

这是关键,但我们说一句人话就够了:

infer R = “如果能匹配成功,就把里面的类型取出来,叫它 R”


# 举一个你 100% 熟的例子

type T = Promise<number>
1

代入:

Promise<number> extends Promise<infer R>
1

TS 会说:

👉 嗯,结构一样 👉 那我推断出:

R = number
1

# 第 4 步:现在看 ? Awaited<R> : T

我们已经知道:

  • 如果 T 是 Promise → R 是里面的值
  • 如果不是 Promise → 直接返回 T

但这里不是返回 R,而是:

Awaited<R>
1

⚠️ 重点来了:

它又把 R 再丢回 Awaited 里算一次

这一步叫:递归


# 第 5 步:完整走一遍(最重要)

# 🌰 示例 1:普通 Promise

type A = Awaited<Promise<number>>
1

计算过程:

# 第一次
T = Promise<number>
1

✔ 是 Promise 👉 R = number 👉 结果 = Awaited<number>


# 第二次
T = number
1

❌ 不是 Promise 👉 走 : T

结果 = number
1

✅ 最终结果:number


# 🌰 示例 2:Promise 套 Promise(这就是它存在的意义)

type B = Awaited<Promise<Promise<string>>>
1
# 第一次
T = Promise<Promise<string>>
R = Promise<string>
→ Awaited<Promise<string>>
1
2
3
# 第二次
T = Promise<string>
R = string
→ Awaited<string>
1
2
3
# 第三次
T = string
❌ 不是 Promise
→ string
1
2
3

✅ 最终结果:string


# 现在用一句大白话翻译整句代码

type Awaited<T> =
  T extends Promise<infer R>
    ? Awaited<R>
    : T
1
2
3
4

⬇️

如果 T 是 Promise,就把它拆开 如果拆开后里面还是 Promise,就继续拆 直到拆到不是 Promise 为止 然后返回那个值


# 和 await 的关系(你马上就通)

JS 里:

const res = await await await promise
1

TS 里:

type Res = Awaited<typeof promise>
1

👉 一模一样的思想


# 你现在只需要记住这 3 句话(真的)

  • 1️⃣ infer = 帮我把里面的类型取出来
  • 2️⃣ Awaited<R> = 继续拆,直到不能拆
  • 3️⃣ 整个类型 = 模拟 JS 的 await 行为
上次更新: 2026/01/07, 09:20:46
extends的约束、条件、分发

← extends的约束、条件、分发

Copyright © 2015-2026 Glitz Ma
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式