考察如下类型:

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. linux内核开发程序风格

    变量命名法 这里是linux不是windows,所以匈牙利命名法是不允许使用的,在内核中,局部变量只要可以明确表达自己的意思,可以使用idx,i这种名字的id, 全局函数和变量需要有表达性的名字例如g ...

  2. Java第九周总结

  3. swing中的按钮事件

    package pack2; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax ...

  4. RabbitMQ-rabbitmqctl和插件使用(四)

    rabbitmqctl 说明 进入mq的bin目录 cd /usr/local/Cellar/rabbitmq/3.7.8/sbin ./rabbitmqctl [-n node] [-t timeo ...

  5. springboot 2.x 集成 drools 7.x

    简介:springboot 2.0.4.RELEASE 集成 drools 7.11.0.Final 1.项目结构 2. pom.xml文件 <?xml version="1.0&qu ...

  6. JVM内存分布和垃圾回收

    内存区域划分   程序计数器(Program counter Register) 描述  程序计数器(Program Counter Register)是一块较小的内存空间.它可以看作是当前线程执行的 ...

  7. linux下nginx+svn

    http://fengqi.me/unix/23.html 因为没有什么可以定制的, 所以svn直接使用系统自带的包管理软件安装, 以centos系列为例, 命令如下: yum install sub ...

  8. GNS3 使用SecureCRT

    "D:\SecureCRT\SecureCRT.EXE" /SCRIPT D:\SecureCRT\DyRouter.vbs /ARG %d /T /TELNET %h %p st ...

  9. js滚轮换切屏

    因为全项目不是自己写的,仅仅是帮别人写js滚轮代码,并且别人项目也还未上线.所以仅仅贴出自己写的那段部分代码, 效果:鼠标滚轮滚动时.网頁屏幕一屏一屏的上下切换 (下面代码在本地电脑的IE,chrom ...

  10. Wireshark 抓包遇到 you don’t have permission to capture on that device mac 错误的解决方案

    Wireshark 抓包遇到 you don’t have permission to capture on that device mac 错误的解决方案 上次有篇博客讲了如何利用wireshark ...