Typescript进阶:解锁Infer类型的使用
Infer特性是在 TypeScript 2.8 版本中引入的,由Ahejlsberg提交的PR,Ahejlsberg是微软技术研究员和TypeScript的首席架构师,C#、Delphi和Turbo Pascal的原创设计师。
https://github.com/Microsoft/TypeScript/pull/21496
infer是什么
infer 关键字用于在条件类型(conditional types)中声明一个类型变量,并从待推断的类型(type to be inferred)中推断出该类型。它的作用是从类型关系中提取出信息,用以满足条件类型的逻辑需求。
这里面有几个重点:
-
用于条件类型中:infer 只能用于 extends 条件类型中。 -
声明类型变量:infer 后跟一个类型变量,这个变量会用来接收被推断出来的类型。 -
从类型中推断:infer 的作用是从符合条件的类型中“提取”出具体的类型,类似于函数的参数解构。
简单来说,它就像是一种“类型占位符”,可以自动帮你在复杂的类型表达式中找到你想要的类型,然后把它提取出来。
例子解析
案例1:数组类型推导
type ArrayElement<T extends any[]> = T extends (infer Element)[] ? Element : never;
效果:
type NumArray = number[];
type StrArray = string[];
type BoolArray = boolean[];
type EmptyArray = never[];
type NotArray = 42;
type First = ArrayElement<NumArray>; // number
type Second = ArrayElement<StrArray>; // string
type Third = ArrayElement<BoolArray>; // boolean
type Empty = ArrayElement<EmptyArray>; // never (因为 never[] 的元素类型是 never)
type Not = ArrayElement<NotArray>; // never (因为 NotArray 不是数组类型)
这个类型的拆分理解如下:
-
T extends any[]
:这部分指定了T
是一个数组类型。any[]
表示数组,而T extends any[]
表示T
是扩展自数组的类型,即T
是某种数组。 -
T extends (infer Element)[]
:这是条件类型的核心部分。这里,我们使用infer
关键字来推断数组T
的元素类型。(infer Element)[]
的意思是“如果T
是一个数组,那么推断这个数组的元素类型为Element
”。 -
? Element
:如果条件T extends (infer Element)[]
为真,即T
确实是一个数组,那么类型为Element
,也就是数组的元素类型。 -
: never
:如果条件为假,即T
不是一个数组,那么类型为never
。never
是一个表示“永不存在的类型”的类型,它通常用于表示错误的类型或不可能出现的情况。
案例2:函数返回类型
ReturnType
是一个TS内置的类型,其内部实现如下
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
效果:
type Func1 = () => number;
type Func2 = (a: string, b: number) => boolean;
type Func3 = (x: any) => void;
type Func4 = (message: string) => Promise<string>;
type Func5 = <T>(item: T) => T[];
type Returned1 = ReturnType<Func1>; // number
type Returned2 = ReturnType<Func2>; // boolean
type Returned3 = ReturnType<Func3>; // void
type Returned4 = ReturnType<Func4>; // Promise<string>
type Returned5 = ReturnType<Func5>; // T[]
这个类型的拆分理解如下:
-
T extends (...args: any[]) => any
:这部分指定了T
是一个函数类型,这个函数可以接收任意数量的参数(...args: any[]
),并且返回任意类型(any
)。这里的...args
表示函数的参数是一个任意长度的参数列表。 -
T extends (...args: any[]) => infer R
:这是条件类型的核心部分。这里,我们使用infer
关键字来推断函数T
的返回类型。(...args: any[]) => infer R
的意思是“如果T
是一个函数,那么推断这个函数的返回类型为R
”。 -
? R
:如果条件T extends (...args: any[]) => infer R
为真,即T
确实是一个函数,那么类型为R
,也就是函数的返回类型。 -
: any
:如果条件为假,即T
不是一个函数,那么类型为any
。这是一种保守的回退选项,但实际上,由于我们已经有了T extends (...args: any[]) => any
的约束,这种情况是不会发生的。
与这个类似的,TS还有一个内置的通用的工具方法,从函数类型中提取参数类型,并将它们作为一个元组类型
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
例子:
type Func1 = (a: number, b: string) => void;
type Func2 = (x: boolean, y: number, z: string) => boolean;
type Params1 = Parameters<Func1>; // [number, string]
type Params2 = Parameters<Func2>; // [boolean, number, string]
其与获取返回值最大的不同在于T extends (...args: infer P) => any ? P : never;
-
T extends (...args: infer P) => any:它的意思是“如果 T 是一个函数,那么推断这个函数的参数类型为 P”。 -
? P:如果条件 T extends (...args: infer P) => any 为真,即 T 确实是一个函数,那么类型为 P,也就是函数的参数类型列表。 -
: never:如果条件为假,即 T 不是一个函数,那么类型为 never。这是一种保守的回退选项,但实际上,由于我们已经有了 T extends (...args: any) => any 的约束,这种情况是不会发生的。
如果没有infer
对于上面提到的例子2,如果没有Infer,要实现类似 ReturnType<T>
的功能,我们只能通过列举的方式来实现。
type GetReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => string ? string :
T extends (...args: any[]) => number ? number :
T extends (...args: any[]) => boolean ? boolean :
any;
// 测试
const test1: GetReturnType<() => number> = 123; // 推断为 number
const test2: GetReturnType<() => string> = "Hello"; // 推断为 string
const test3: GetReturnType<() => boolean> = true; // 推断为 boolean
这里通过多层条件类型手动匹配返回类型。所以infer还是很有帮助的,它能节省很多的判断类型。
总结
infer 就是 TypeScript 的“自动推理器”,它帮你在类型定义里自动找出某个特定的类型,而不需要你手动去指定。你只需要告诉 TypeScript 去哪个位置推断类型,它就会帮你填上正确的类型。其好处可以归纳为这几点:
-
灵活性:infer 提供了一种灵活的方式来处理和构建类型,特别是在处理泛型时。 -
代码复用:通过使用 infer,可以创建更通用的类型实用程序,这些实用程序可以在多个地方重用。 -
类型安全:infer 可以帮助确保类型的正确性,减少类型错误的可能性。
另外需要记住infer仅能用于 extends 子句的推断,其他地方会报错。
本文由 mdnice 多平台发布