TypeScript 使用 infer 实现类型推断

首先从一个问题引入,下面有一个模拟发送请求的方法,返回内容被包裹在 Promise 里面返回,我们怎样拿到 Promise 里面包裹的类型呢?

type ResponseEntity<T> = {
    success: boolean;
    result: T | null;
    message: string;
}

function request<T>(req: T, timeout: number) {
    return Promise.race([
        new Promise<ResponseEntity<T>>(resolve => setTimeout(() => resolve({
            success: true,
            result: req,
            message: ""
        }), 3000)),
        new Promise<ResponseEntity<T>>((_, reject) => setTimeout(() => reject({
            success: false,
            result: null,
            message: "请求超时"
        }), timeout))
    ])
}

很多同学上来就用 typeofReturnType 一把梭哈:

type Request = typeof request; // type Request = <T>(req: T, timeout: number) => Promise<ResponseEntity<T>>
type ReturnRequest = ReturnType<Request>; // type ReturnRequest = Promise<ResponseEntity<unknown>>

这样搞了之后最终只能拿到返回类型 Promise<ResponseEntity<unknown>> ,对于 Promise 里面的泛型参数就无能为力了。这个时候就要用到 infer 关键字进行类型推断。

所谓推断,就是你不用预先指定在泛型列表中,在运行时会自动判断,不过你得先预定义好整体的结构。举个例子

type Foo<T> = T extends {t: infer Test} ? Test: string

首选看 extends 后面的内容,{t: infer Test} 可以看成是一个包含 t 属性的类型定义,这个 t 属性的 value 类型通过 infer 进行推断后会赋值给 Test 类型,如果泛型实际参数符合 {t: infer Test} 的定义那么返回的就是 Test 类型,否则默认给缺省的 string 类型。

举个例子加深下理解

type One = Foo<number>  // string,因为number不是一个包含t的对象类型
type Two = Foo<{t: boolean}>  // boolean,因为泛型参数匹配上了,使用了infer对应的type
type Three = Foo<{a: number, t: () => void}> // () => void,泛型定义是参数的子集,同样适配

使用 infer 实际上就是定义了一个泛型函数,通过传入类型参数,然后返回指定的类型。infer 用来对满足的泛型类型进行子类型的抽取,有很多高级的泛型工具也巧妙的使用了这个方法。

回到最开始的问题,我们定义一个 infer 结构:

type Awaited<T> = T extends Promise<infer U> ? U : T;

然后给这个结构传入实际参数:

type Res = Awaited<ReturnRequest>;
/**
 * type Res = {
 *  success: boolean;
 *  result: unknown;
 *  message: string;
 * }
 */

这样我们就拿到了 Promise 内部的泛型参数。

再说一下 TS如何获取第三方库未导出的 Type 这篇文章中的问题,获取函数参数,我们使用了 Parameters<T> ,这样得到的是一个函数参数类型的元组,还得通过下标去拿对应的类型,感觉不是很优雅:

type RadioProps = React.ComponentProps<typeof Radio.Group>;
type ChangeEvent = RadioProps["onChange"];
type ChangeFnParams = Parameters<NonNullable<ChangeEvent>>[0];

看了一下 Parameters<T> 的定义:

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

可以看到 args 的类型是一个数组,因此推断出来的是一个参数类型的元组。我们希望在只有一个参数的时候直接拿到类型,因此改写了 Parameters<T> 的定义如下:

type Param<T> = T extends (arg: infer U) => any ? U : never;

这样就可以直接取到参数类型了:

type RadioProps = React.ComponentProps<typeof Radio.Group>;
type ChangeEvent = RadioProps["onChange"];
type Param<T> = T extends (args: infer U) => any ? U : never;
type ChangeFnParams = Param<NonNullable<ChangeEvent>>; // type ChangeFnParams = RadioChangeEvent

顺便说一下 TS 内置的工具类型是怎么做的,ReturnType 是这样定义的:

type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

Parameters 的定义如下:

type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never

ConstructorParameters 定义如下:

type ConstructorParameters<T extends abstract new (...args: any) => any> = T extends abstract new (...args: infer P) => any ? P : never

参考:

TypeScript 高级用法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值