首先从一个问题引入,下面有一个模拟发送请求的方法,返回内容被包裹在 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))
])
}
很多同学上来就用 typeof
和 ReturnType
一把梭哈:
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