在条件类型中使用 infer 关键字

前端精髓

共 2926字,需浏览 6分钟

 ·

2021-04-08 20:40


在 TypeScript 中条件类型的用法是:

T extends U ? X : Y

跟 JS 中的条件表达式一样,如果 extends 语句为真,则取X类型 ,反之得到Y类型 。我们这里把X称为条件类型的真分支,Y 称为假分支。


现在,在 TypeScript 2.8 之后,我们可以在 extends 条件语句中使用 infer 关键字引入一个变量表示推断的类型,这个变量可以被用在真分支中,也就是说 infer 实际上是一个声明关键字,我们可以用它来声明一个变量,而该变量表示的是 infer 所处位置的类型。


以标准库的 ReturnType 为例:

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
const getFullName = (firstName: string, lastName: string): string { return `${firstName}_${lastName}`;}const fullName: ReturnType<typeof getFullName> = getFullName('foo', 'bar');


这里 ReturnType<T> 接收一个任意函数,在 extends 分支把推断的函数返回值的类型赋给变量 R,从而得到该类型。这里需要注意的是,我们只能在 extends 条件语句中使用 infer 关键字,不能在诸如类型参数这样的地方使用它:

type ReturnType<T extends (...args: any[]) => infer R> = R;  // Error.


既然可以获取到函数的返回值的类型,同样也可以推断出函数的参数类型,标准库的Paramaters<T>把函数的每个参数类型提取到一个元组中:

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
const getFullName = (firstName: string, lastName: string, age: number): string =>{ return `${firstName}_${lastName}`;}type params = Parameters<typeof getFullName> // [string, string, number]


除了把函数参数列表的类型提取出来,进一步深入,假如函数的参数是单个对象,我们可以利用 infer 把参数的结构推断出来:

type FunctionWithMappedArgument<P extends { [key: string]: any }> = (args: P) => any;type DestructuredArguments<F extends FunctionWithMappedArgument<any>> = F extends FunctionWithMappedArgument<infer R> ? R : never;
declare function drawPoint(config: { x: number, y: number, color: string}): any;const args: DestructuredArguments<typeof drawPoint> = { x: 4, y: 6,}


这里我们先定义出参数类型为单个对象的通用函数 FunctionWithMappedArgument<T>,接着定义解构参数的方法 DestructuredArguments<T>,这个方法做的事情是接收一个FunctionWithMappedArgument<T> 类型的函数,然后把函数的泛型参数T推断为新的变量 R,这样编译器就会替我们计算出 R 的解构。


通过上面的代码,我们就可以把函数的参数类型提取出来,无需再声明一个类型,编码过程中编辑器的智能感知也能很好地进行代码补全提示。


多处 infer 推断一个变量

上面展示的例子都只用到了一个 infer,在 extends 条件语句中,我们可以有多个 infer,只不过它们只能作用于同一个变量,根据推断位置的不同产生的类型也有所不同。


在共变的位置,会推断出联合类型:

type Foo<T> = T extends { a: infer U, b: infer U } ? U : never;type T10 = Foo<{ a: string, b: string }>;  // stringtype T11 = Foo<{ a: string, b: number }>;  // string | number


在逆变的位置,推断的是交叉类型:

type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>;  // stringtype T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>;  // string & number


函数重载中的推断

当作用于一个有多处调用签名(函数重载)的类型时,infer 只对最后一个签名生效,不可能基于参数列表的不同进行重载。

declare function foo(x: string): number;declare function foo(x: number): string;declare function foo(x: string | number): string | number;type T30 = ReturnType<typeof foo>;  // string | number


浏览 24
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报