考察如下类型:

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. Ztree加载完成默认选中根节点右侧生成表格

    需求:页面加载完成之后,默认选中ztree的根节点,并执行其点击方法,右侧生成表格: 效果:如下图所示: 思路:在节点点击事件clickNode方法中根据节点的部门code查询这个部门下的所有员工,并 ...

  2. 集合:Collection

    why ? when ? how ? what ? Java 集合框架图 由上图我们可以看到,Java 集合主要分为两类:Collection 和 Map. Collection 接口 遍历 Coll ...

  3. linux初步学习有感

    经过了一段时间对linux的接触,从最开始接触到的deepin到后来我最喜欢的KaliLinux,感受到了这个我曾经并不了解的操作系统的独特魅力. 我是到了大学才知道linux这个系统的,但是在小时候 ...

  4. * format-- set command window output display format

    the displayed number may not match the input number due to display format default: 4 decimal syntax: ...

  5. pandas处理各类表格数据

    经常遇到Python读取excel和csv还有其他各种文件的内容.json还有web端的读取还是比较简单,但是excel和csv的读写是很麻烦.这里记录了pandas库提供的方法来实现文本内容和Dat ...

  6. 51NOD 1183编辑距离(动态规划)

    >>点击进入原题测试<< 思路:这个题放在基础题,分值还是零分,好歹也给人家动态规划一点面子啊!刚开始写的想法是找到其最大公共字串,然后用两个字符串中最长字符串的长度减掉最大公 ...

  7. 3.8.5 多重选择:switch语句

        在处理多个选项时,使用if/else结构显得有些笨拙.               Scanner in = new Scanner(System.in);             Syste ...

  8. 第2章 取得大家的支持 录播感悟(意外的Sprint)

    关于<取得大家的支持>这个故事我看了三遍,做了计划,做了时间轴,因为之前有过第1章<知易行难>的沟通和磨合后,相信会顺利很多吧!可是却是意外不断的发生: 1.本人车钥匙掉停车场 ...

  9. 【Codeforces 598D】Igor In the Museum

    [链接] 我是链接,点我呀:) [题意] 题意 [题解] 同一个联通块里面答案都一样. 把每个联通块的答案都算出来 然后赋值就好 [代码] #include <bits/stdc++.h> ...

  10. 【驱动开发】file_operations ---linux 2.6.30

    路径: linux-2.6.30/include/linux/fs.h struct file_operations { struct module   *owner; loff_t         ...