考察如下类型:

type PromiseType<T> = (args: any[]) => Promise<T>;

那么对于符合上面类型的一个方法,如何得知其 Promise 返回的类型?

譬如对于这么一个返回 string 类型的 Promise:

async function stringPromise() {
return "string promise";
}

RetrunType

如果你对 TypeScript 不是那么陌生,可能知道官方类型库中提供了 RetrunType 可获取方法的返回类型,其用法如下:

type stringPromiseReturnType = ReturnType<typeof stringPromise>; // Promise<string>

确实拿到了方法的返回类型,不过是 Promise<string>。但其实是想要返回里面的 string,所以和我们想要的还差点意思。

既然都能从一个方法反解其返回类型,肯定还能从 Promsie<T> 中反解出 T。所以不不妨看看 ReturnType 的定义:

/**
* Obtain the return type of a function type
*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

F12 一看,果然发现了点什么,这里使用了 infer 关键字。

条件类型及 infer

上面 T extends U ? X : Y 的形式为条件类型(Conditional Types),即,如果类型 T 能够赋值给类型 U,那么该表达式返回类型 X,否则返回类型 Y

所以,考察 ReturnType的定义,

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

如果传入的类型 T 能够赋值给 (...args: any) => R 则返回类型 R

但是这里类型 R 从何而来?讲道理,泛型中的变量需要外部指定,即 RetrunType<T,R>,但我们不是要得到 R 么,所以不能声明在这其中。这里 infer 便解决了这个问题。表达式右边的类型中,加上 infer 前缀我们便得到了反解出的类型变量 R,配合 extends 条件类型,可得到这个反解出的类型 R。这里 R 即为函数 (...args: any) => R 的返回类型。

反解 Promise

有了上面的基础,推而广之就很好反解 Promise<T> 中的 T 了。

type PromiseType<T> = (args: any[]) => Promise<T>;

type UnPromisify<T> = T extends PromiseType<infer U> ? U : never;

测试 UnPromisify<T>

async function stringPromise() {
return "string promise";
} async function numberPromise() {

return 1;

} interface Person {

name: string;

age: number;

} async function personPromise() {

return { name: "Wayou", age: 999 } as Person;

} type extractStringPromise = UnPromisify<typeof stringPromise>; // string type extractNumberPromise = UnPromisify<typeof numberPromise>; // number type extractPersonPromise = UnPromisify<typeof personPromise>; // Person

解析参数数组的类型

反解还可用在其他很多场景,比如解析函数入参的类型。

type VariadicFn<A extends any[]> = (...args: A) => any;
type ArgsType<T> = T extends VariadicFn<infer A> ? A : never; type Fn = (a: number, b: string) => string;

type Fn2Args = ArgsType<Fn>; // [number, string]

另一个示例

假设我们编写了两个按钮组件,底层渲染的是 HTML 原生的 buttona 标签。为了组件最大化可定制,原生元素支持的属性该组件也需要支持,因此可这样来写组件的 props:

type ButtonProps = {
color: string;
children: React.ReactChildren;
} & React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>; type AnchorButtonProps = {

color: string;

disabled: boolean;

children: React.ReactChildren;

} & React.DetailedHTMLProps<

React.AnchorHTMLAttributes<HTMLAnchorElement>,

HTMLAnchorElement

>; export function Button({ children, ...props }: ButtonProps) {

//...

return <button {...props}>{children}</button>;

} export function AnchorButton({ children, ...props }: AnchorButtonProps) {

//...

return <a {...props}>{children}</a>;

}

单看 ButtonAnchorButton 的属性,

type ButtonProps = {
color: string;
children: React.ReactChildren;
} & React.DetailedHTMLProps<
React.ButtonHTMLAttributes<HTMLButtonElement>,
HTMLButtonElement
>; type AnchorButtonProps = {

color: string;

disabled: boolean;

children: React.ReactChildren;

} & React.DetailedHTMLProps<

React.AnchorHTMLAttributes<HTMLAnchorElement>,

HTMLAnchorElement

>;

不难看出两者是有共性的,即可抽取成如下的形式:

type ExtendHTMLAttributes<P, T, K> = P & React.DetailedHTMLProps<T, K>;

其中 T 呢又是 T<K> 形式,即 T 中包含或有使用了 K。因此对使用者来说,如果传递了 T<K> 形式,就没必要单独再传递一次 K,我们应该是能利用 inferT<K> 解析出 K 的。

T extends React.HtmlHTMLAttributes<infer K> ? K : HTMLElement

所以抽取出来两种组件 Props 可公用的一个类型如下:

export type ExtendHTMLAttributes<
/** 组件自定义属性 */
P,
/** 原生 HTML 标签自有属性 */
T extends React.HtmlHTMLAttributes<HTMLElement>
> = P &
React.DetailedHTMLProps<
T,
T extends React.HtmlHTMLAttributes<infer K> ? K : HTMLElement
>;

利用抽取的 ExtendHTMLAttributes,两种按钮的 Props 可重新书写成如下形式:

type ButtonProps = ExtendHTMLAttributes<
{
color: string;
children: React.ReactChildren;
},
React.ButtonHTMLAttributes<HTMLButtonElement>
>; type AnchorButtonProps = ExtendHTMLAttributes<

{

color: string;

disabled: boolean;

children: React.ReactChildren;

},

React.AnchorHTMLAttributes<HTMLAnchorElement>

>;

去掉了两者重叠的部分,看起来简洁了一些。关键后续编写其他组件时,如果想支持原生 HTML 属性,直接复用这里的 ExtendHTMLAttributes 类型即可。

相关资源

TypeScript `infer` 关键字的更多相关文章

  1. typescript 中的 infer 关键字的理解

    infer 这个关键字,整理记录一下,避免后面忘记了.有点难以理解呢. infer infer 是在 typescript 2.8中新增的关键字. infer 可以在 extends 条件类型的字句中 ...

  2. [TypeScript] Infer the Return Type of a Generic Function Type Parameter

    When working with conditionals types, within the “extends” expression, we can use the “infer” keywor ...

  3. 白话typescript中的【extends】和【infer】(含vue3的UnwrapRef)

    大家好,我是小雨小雨,致力于分享有趣的.实用的技术文章. 内容分为翻译和原创,如果有问题,欢迎随时评论或私信,希望和大家一起进步. 分享不易,希望能够得到大家的支持和关注. extends types ...

  4. TypeScript 类型推导及类型兼容性

    类型推导就是在没有明确指出类型的地方,TypeScript编译器会自己去推测出当前变量的类型. 例如下面的例子: let a = 1; 我们并没有明确指明a的类型,所以编译器通过结果反向推断变量a的类 ...

  5. TypeScript 泛型及应用

    TypeScript 泛型及应用 一.泛型是什么 二.泛型接口 三.泛型类 四.泛型约束 4.1 确保属性存在 4.2 检查对象上的键是否存在 五.泛型参数默认类型 六.泛型条件类型 七.泛型工具类型 ...

  6. TypeScript学习文档-基础篇(完结)

    目录 TypeScript学习第一章:TypeScript初识 1.1 TypeScript学习初见 1.2 TypeScript介绍 1.3 JS .TS 和 ES之间的关系 1.4 TS的竞争者有 ...

  7. typeScript学习随笔(一)

    TypeScript学习随笔(一) 这么久了还不没好好学习哈这么火的ts,边学边练边记吧! 啥子是TypeScript  TypeScript 是 JavaScript 的一个超集,支持 es6 标准 ...

  8. typescripts学习

    可选与默认参数 可选参数:在参数名后面,冒号前面添加一个问号,则表明该参数是可选的.如下代码: function buildName(firstName: string, lastName?: str ...

  9. 作为一个新手的Oracle(DBA)学习笔记【转】

    一.Oracle的使用 1).启动 *DQL:数据查询语言 *DML:数据操作语言 *DDL:数据定义语言 DCL:数据控制语言 TPL:事务处理语言 CCL:指针控制语言 1.登录 Win+R—cm ...

随机推荐

  1. ubuntu jdk和tomcat配置

    先查看linux的版通过file /sbin/init命令,下载对应版本的jdk. 我的ubuntu是64位的(桌面系统),所以下载的是jdk-7u71-linux-x64.tar.gz 在home的 ...

  2. 洛谷 4172 [WC2006]水管局长

    [题解] 我们把操作倒过来做,就变成了加边而不是删边.于是用LCT维护动态加边的最小生成树就好了.同样要注意把边权变为点权. #include<cstdio> #include<al ...

  3. Shiro_DelegatingFilterProxy

    1.DelegatingFilterProxy实际上是Filter的一个代理对象.默认情况下,Spring会到IOC容器中查找与<filter-name>对应的filter bean.也可 ...

  4. [luoguP1316] 丢瓶盖(二分答案)

    传送门 二分答案再判断即可 ——代码 #include <cstdio> #include <iostream> #include <algorithm> #def ...

  5. python——re模块(正则表达式)

    re 模块的使用: 1.使用compile()函数编译一个parttern对象, 例如:parttern=re.compile(r'\d+') 2.通过pattern对象提供的一系列属相和方法,对文本 ...

  6. nyoj_528_找球号(三)_201404152050

    找球号(三) 时间限制:2000 ms  |  内存限制:3000 KB 难度:2   描述 xiaod现在正在某个球场负责网球的管理工作.为了方便管理,他把每个球都编了号,且每个编号的球的总个数都是 ...

  7. 【python】range的用法

    range的用法: >>> range(1,5) #代表从1到5(不包含5)[1, 2, 3, 4]>>> range(1,5,2) #代表从1到5,间隔2(不包含 ...

  8. Android 四大组件学习之Activity六

    本节学习Activity的状态保存与恢复. 先用样例開始: 布局文件主要是实现例如以下.大家自行编写 Activity逻辑代码: public class FiveActivity extends A ...

  9. 路由其实也可以很简单-------Asp.net WebAPI学习笔记(一) ASP.NET WebApi技术从入门到实战演练 C#面向服务WebService从入门到精通 DataTable与List<T>相互转换

    路由其实也可以很简单-------Asp.net WebAPI学习笔记(一)   MVC也好,WebAPI也好,据我所知,有部分人是因为复杂的路由,而不想去学的.曾经见过一位程序猿,在他MVC程序中, ...

  10. nginx安装【windows下安装】

    http://nginx.org/en/download.html 解压: 2.启动nginx 有很多种方法启动nginx (1)直接双击nginx.exe,双击后一个黑色的弹窗一闪而过[不是失败就是 ...