考察如下类型:

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. 面试总结——Java高级工程师(二)

    一.Java底层基础题 1.SpringMVC的原理以及返回数据如何渲染到jsp/html上? 答:Spring MVC的核心就是 DispatcherServlet , 一个请求经过 Dispatc ...

  2. Go:字符串操作

    Package strings:https://golang.google.cn/pkg/strings/ package main import ( "fmt" "st ...

  3. LES on MCT

  4. array copy rotate in Pointwise

    goal: # # Copyright 2010 (c) Pointwise, Inc. # All rights reserved. # # This sample Pointwise script ...

  5. 2.8 补充:shell变量引用方式

    一 变量   变量:本质上是程序中保存用户数据的一块内存空间,变量名就是内存空间地址.   Shell中:变量可由字母数字和下划线组成,以字母或下划线开头.   命名:PATH=/sbin       ...

  6. Spring Security核心类关系图

    以有限的脑力记忆无限的Knowledge,多画图,多画图,多画图. 核心类Authentication 和 GrantedAuthority AbstractAuthenticationToken 由 ...

  7. 矩形面积求并(codevs 3044)

    题目描述 Description 输入n个矩形,求他们总共占地面积(也就是求一下面积的并) 输入描述 Input Description 可能有多组数据,读到n=0为止(不超过15组) 每组数据第一行 ...

  8. 【BZOJ2434】阿狸的打字机(fail树,DFS序)

    题意: 1<=N<=10^5 1<=M<=10^5 输入总长<=10^5   思路: From http://blog.csdn.net/lych_cys/article ...

  9. [bzoj1833][ZJOI2010]数字计数(数位DP)

    题目:http://www.lydsy.com:808/JudgeOnline/problem.php?id=1833 分析:简单的数位DP f[i][j][k]表示在i位数.最高位j的所有数字中k的 ...

  10. Abstract factory抽象工厂--对象创建型

    意图: 提供一个创建一系列相关或者相互依赖对象的接口,而无需指定它们具体的类. 别名:Kit 补充: 抽象产品A : (产品A1 和产品 A2) 抽象产品B : ( 产品B1 和 产品B2) 一般情况 ...