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 代码。

相关推荐
white-persist5 分钟前
Python实例方法与Python类的构造方法全解析
开发语言·前端·python·原型模式
新中地GIS开发老师43 分钟前
Cesium 军事标绘入门:用 Cesium-Plot-JS 快速实现标绘功能
前端·javascript·arcgis·cesium·gis开发·地理信息科学
Superxpang1 小时前
前端性能优化
前端·javascript·vue.js·性能优化
Rysxt_1 小时前
Element Plus 入门教程:从零开始构建 Vue 3 界面
前端·javascript·vue.js
隐含1 小时前
对于el-table中自定义表头中添加el-popover会弹出两个的解决方案,分别针对固定列和非固定列来隐藏最后一个浮框。
前端·javascript·vue.js
大鱼前端1 小时前
Turbopack vs Webpack vs Vite:前端构建工具三分天下,谁将胜出?
前端·webpack·turbopack
你的人类朋友1 小时前
先用js快速开发,后续引入ts是否是一个好的实践?
前端·javascript·后端
知识分享小能手1 小时前
微信小程序入门学习教程,从入门到精通,微信小程序核心 API 详解与案例(13)
前端·javascript·学习·react.js·微信小程序·小程序·vue
子兮曰2 小时前
npm workspace 深度解析:与 pnpm workspace 和 Lerna 的全面对比
前端·javascript·npm