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
的原理与注意事项
- 只能在条件类型中使用,不能单独出现。
- 只能用于类型推断,不能直接用于类型声明。
- 可以多次使用,比如提取元组的第一个和剩余部分:
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
的常见应用场景
- 类型工具库的实现 (如 TypeScript 内置的
ReturnType
、Parameters
、InstanceType
等)。 - 类型递归处理(如递归解包 Promise、递归处理嵌套数组等)。
- 类型映射与变换(如提取对象属性、元组分割等)。
- 字符串模板类型处理(如提取字符串前缀、后缀等)。
六、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 代码。