文章

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)

其实你每天都在用

  1. ReturnType<typeof someFn>:第三方库没导出函数返回值类型时自己提取
  2. Parameters<typeof onClick>[0]:封装高阶组件时提取事件处理函数的参数类型
  3. Awaited<ReturnType<typeof fetch>>:去掉 async 函数返回的 Promise 层
  4. Vue defineEmits 类型推导:{ submit: [data: FormData] } 内部用 infer 提取参数元组
  5. 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 进行授权