TypeScript 中 infer 关键字

TypeScript 中的 infer 关键字是用于条件类型(Conditional Types)中的一种高级用法,主要用于在类型推断时提取类型信息。下面我会详细讲解 infer 的用法、原理、常见场景,并配合大量代码示例,帮助你深入理解。


一、infer 的基本语法

infer 只能在条件类型(即 extends 语句后面的三元表达式)中使用,用于声明一个待推断的类型变量。其基本语法如下:

typescript 复制代码
type MyType<T> = T extends SomeType<infer U> ? U : OtherType;

这里的 infer U 表示:如果 T 能够匹配 SomeType<...> 这种结构,那么就把尖括号里的类型推断出来,赋值给 U,并在 ? 后面使用它。


二、经典案例详解

1. 提取数组元素类型

typescript 复制代码
type ElementType<T> = T extends Array<infer U> ? U : never;

type A = ElementType<number[]>; // number
type B = ElementType<string[]>; // string
type C = ElementType<boolean>;  // never

解释:

  • 如果 T 是数组类型(如 number[]),则推断出元素类型 U
  • 如果不是数组类型,则返回 never

2. 提取函数返回值类型

typescript 复制代码
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

type Fn = (a: number, b: string) => boolean;
type Result = ReturnType<Fn>; // boolean

解释:

  • 如果 T 是函数类型,则推断其返回值类型 R
  • 否则返回 any

3. 提取函数参数类型

typescript 复制代码
type Parameters<T> = T extends (...args: infer P) => any ? P : never;

type Fn = (a: number, b: string) => boolean;
type Params = Parameters<Fn>; // [number, string]

解释:

  • 如果 T 是函数类型,则推断其参数类型组成的元组 P

4. 提取 Promise 的结果类型

typescript 复制代码
type UnpackedPromise<T> = T extends Promise<infer U> ? U : T;

type A = UnpackedPromise<Promise<number>>; // number
type B = UnpackedPromise<string>;          // string

5. 提取对象属性类型

typescript 复制代码
type PropertyType<T, K extends keyof T> = T extends { [P in K]: infer U } ? U : never;

type Obj = { name: string; age: number };
type NameType = PropertyType<Obj, 'name'>; // string

6. 递归提取嵌套 Promise 的最终类型

typescript 复制代码
type DeepUnpacked<T> = T extends Promise<infer U> ? DeepUnpacked<U> : T;

type A = DeepUnpacked<Promise<Promise<Promise<number>>>>; // number

三、infer 的原理与注意事项

  1. 只能在条件类型中使用,不能单独出现。
  2. 只能用于类型推断,不能直接用于类型声明。
  3. 可以多次使用,比如提取元组的第一个和剩余部分:
typescript 复制代码
type FirstAndRest<T> = T extends [infer First, ...infer Rest] ? [First, Rest] : never;

type R = FirstAndRest<[1, 2, 3]>; // [1, [2, 3]]

四、复杂案例与实战

1. 提取类的实例类型

typescript 复制代码
class Person {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

type InstanceType<T> = T extends new (...args: any[]) => infer R ? R : any;

type P = InstanceType<typeof Person>; // Person

2. 提取联合类型中的某一部分

typescript 复制代码
type ExtractArrayType<T> = T extends (infer U)[] ? U : never;

type A = ExtractArrayType<string[] | number[]>; // string | number

3. 提取函数的 this 类型

typescript 复制代码
type ThisParameterType<T> = T extends (this: infer U, ...args: any[]) => any ? U : unknown;

function fn(this: Date, x: number) {}
type T = ThisParameterType<typeof fn>; // Date

4. 提取元组的最后一个元素

typescript 复制代码
type Last<T extends any[]> = T extends [...infer Rest, infer Last] ? Last : never;

type A = Last<[1, 2, 3]>; // 3

5. 提取字符串字面量类型的前缀

typescript 复制代码
type Prefix<T extends string> = T extends `${infer P}_${string}` ? P : never;

type A = Prefix<'hello_world'>; // 'hello'
type B = Prefix<'foo_bar_baz'>; // 'foo'

五、infer 的常见应用场景

  1. 类型工具库的实现 (如 TypeScript 内置的 ReturnTypeParametersInstanceType 等)。
  2. 类型递归处理(如递归解包 Promise、递归处理嵌套数组等)。
  3. 类型映射与变换(如提取对象属性、元组分割等)。
  4. 字符串模板类型处理(如提取字符串前缀、后缀等)。

六、infer 的局限性

  • 只能在条件类型中使用,不能单独声明类型变量。
  • 只能推断结构明确的类型,对于复杂的联合类型、交叉类型,推断可能不如预期。
  • 推断出来的类型只能在 ? 后面使用,不能带出条件类型作用域。

七、实战演练:自定义类型工具

1. 实现一个类型工具,提取函数的第一个参数类型

typescript 复制代码
type FirstArg<T> = T extends (arg1: infer A, ...args: any[]) => any ? A : never;

type Fn = (x: number, y: string) => void;
type Arg = FirstArg<Fn>; // number

2. 实现一个类型工具,提取对象所有属性的类型组成的联合类型

typescript 复制代码
type ValueOf<T> = T extends { [key: string]: infer V } ? V : never;

type Obj = { a: number; b: string; c: boolean };
type V = ValueOf<Obj>; // number | string | boolean

八、infer 与泛型的区别

  • 泛型用于"传递"类型参数,infer 用于"推断"类型参数。
  • 泛型参数需要调用时显式传递或由上下文推断,infer 只在条件类型内部自动推断。

九、总结

  • infer 是 TypeScript 类型系统中非常强大的工具,能让类型变得更智能和灵活。

  • 它极大地提升了类型工具库的表达能力,适合处理复杂类型变换、提取、递归等场景。

  • 掌握 infer,能让你写出更高级、更健壮的 TypeScript 代码。

相关推荐
拉不动的猪27 分钟前
无缝适配 PC 和移动端‌我们要注意哪些点呢
前端·javascript·面试
酱酱们的每日掘金1 小时前
🔥 4 月精选:AICoding Cursor上新与 MCP 实战揭秘!- AI Coding 周刊第 5 期
前端·ai编程·mcp
天天扭码1 小时前
一分钟解决 | 高频面试算法题——和为 K 的子数组(前缀和)
前端·算法·面试
搞瓶可乐1 小时前
鸿蒙ArkUI之布局实战,线性布局(Column,Row)、弹性布局(Flex)、层叠布局(Stack),详细用法
前端·harmonyos·鸿蒙系统·arkui·弹性布局·布局实战·堆叠布局
Aphasia3112 小时前
小厂面试常考算法题整合(一)✍🏻
前端·算法·面试
五月仲夏2 小时前
React基础知识(补充中)
前端·react.js·前端框架
王富贵的记录2 小时前
React 函数组件和类组件的区别
前端·javascript·react.js
yuhaiqiang2 小时前
在公司写代码是工作,在开源社区写代码是生活
前端·后端
左耳咚2 小时前
Egg.js 服务端 HTML 强缓存问题排查与解决
前端·egg.js
DevUI团队3 小时前
Electron 入门学习指南:快速搭建跨平台桌面应用
前端·javascript·electron