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

在 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 }>;  // string
type 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 }>;  // string
type 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值