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

相关推荐
2501_915373881 小时前
Vue 3零基础入门:从环境搭建到第一个组件
前端·javascript·vue.js
沙振宇3 小时前
【Web】使用Vue3开发鸿蒙的HelloWorld!
前端·华为·harmonyos
运维@小兵4 小时前
vue开发用户注册功能
前端·javascript·vue.js
蓝婷儿4 小时前
前端面试每日三题 - Day 30
前端·面试·职场和发展
oMMh4 小时前
使用C# ASP.NET创建一个可以由服务端推送信息至客户端的WEB应用(2)
前端·c#·asp.net
一口一个橘子5 小时前
[ctfshow web入门] web69
前端·web安全·网络安全
读心悦6 小时前
CSS:盒子阴影与渐变完全解析:从基础语法到创意应用
前端·css
湛海不过深蓝7 小时前
【ts】defineProps数组的类型声明
前端·javascript·vue.js
layman05287 小时前
vue 中的数据代理
前端·javascript·vue.js
柒七爱吃麻辣烫7 小时前
前端项目打包部署流程j
前端