typescript高级用法之infer的理解与使用
前言
以前一直不会用infer,要么直接就是returnType,压根不需要用infer,网上那些教程只给示例不给具体场景就无法让人很好理解这玩意。
类型分发
对于infer,最好应该先说一下类型分发,虽然他们关系不是太大,但是如果把infer与类型分发结合起来,让人一看就觉得这人ts水平可以。至于协变与逆变等概念会比较容易让人搞混乱,可以以后再掌握。
我以前也学过这个,但是并不是能完全掌握它的使用时机,也不知道如何用,所以看别人用能看懂和自己能用完全是2种状态。
首先看一下类型分发的基本例子:
interface Fish {
fish: string
}
interface Water {
water: string
}
interface Bird {
bird: string
}
interface Sky {
sky: string
}
//naked type
type Condition<T> = T extends Fish ? Water : Sky;
let condition1: Condition<Fish | Bird> = { water: '水' };
let condition2: Condition<Fish | Bird> = { sky: '天空' };
相信这个例子大家很容易理解,但是实际中什么时候用,怎么用,完全不知道。
这个例子有个特点,就是下面的condition1和condition2里定义的类型里所传的泛型与后面赋值的类型并不一样。
也就是说,类型分发一般是用来先知道已知类型,赋的值的类型会基于这个分发进行判断推出相应类型。
乍看之下好像还是没什么卵用,比如condition1,我都知道类型,我直接写个Sky|Water类型不香?干嘛二货的还搞个类型分发?
上面那个例子确实没啥卵用,但是如果判断继承的也是泛型,那么就可以快速取出一些类型,而不用自己重新去定义:(虽然这些很多都是内置的)
type Diff<T, U> = T extends U ? never : T;
type R = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
type Filter<T, U> = T extends U ? T : never;
type R1 = Filter<string | number | boolean, number>;
既然有内置的,还不是没卵用。。。所以这就需要和infer联合使用才能看出牛b之处。
infer初探
infer大家应该都知道,returnType就是infer搞得,代码是这样:
type ReturnType<T extends (...args: any[]) => any> = T extends (...args: any[]) => infer R ? R : any;
乍看之下好像有点难懂,其实仔细看发现还是很好理解的,它也是个类型分发。
学到这里,很多人可能就只知道有这个东西,但是什么时候用Infer完全不知道,我也是这样,后来再次听课时突发灵感,发现这个infer其实就相当于占位,也就是一个不知道的类型,用infer X去给他占位,再结合类型分发,就能玩出花样来了。当时还有小伙伴这么问:ts不是能自动推断类型吗?为什么需要Infer X去推断类型。卧槽,这个问的太好了,这个就是理解Infer的关键。
我们先结合个示例来进行说明:
export {}
type Parameters<T> = T extends (...args: infer R) => any ? R : any;
type T0 = Parameters<() => string>; // []
type T1 = Parameters<(s: string) => void>; // [string]
type T2 = Parameters<(<T>(arg: T) => T)>; // [unknown]
这个parameter也是内置的,可以看见,也是个类型分发,跟returnType区别就是infer X的占位跑到参数上去定义类型了。
如果我们把infer R换成已知类型,那么这个类型分发就跟一开始的demo没太大区别:
type Parameters<T> = T extends (...args:string[]) => any ? string[] : any;
type T0 = Parameters<() => string>;
如果不换成已知类型,那么只写R不写infer会报错,因为不知道R是什么东西。
那么如果通过泛型传呢?可惜args必须是个数组类型,所以用泛型传还得限定下它的条件:
type Parameters<T,R extends Array<any>> = T extends (...args:R) => any ? R : any;
type T0 = Parameters<() => string,string[]>;
可以发现,这么传跟已知类型传其实没太大区别,因为在传第二个泛型的时候,这个类型我们是知道的,所以这种情况,也没什么太大用处,除非传泛型的是另一个人,那么我们在写这个库的时候,倒是可以拿到用户所定义的类型。这时倒是有点作用。
这样一换就可以发现,infer可以在类型推导中去占任何位置,最后的推导的类型可以借助这之间所需的类型。可以看下这个例子加深理解:
type T1 = { name: string };
type T2 = { age: number };
type UnionToIntersection<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void } ? U : never;
type T3 = UnionToIntersection<{ a: (x: T1) => void; b: (x: T2) => void }>; // T1 & T2
这个例子就是infer取得参数,两个函数的参数,对于为啥2个会出来交叉类型,这里是协变,所以是交叉类型。
下面看一下更难点的例子,来源于leetcode招聘:
https://github.com/LeetCode-OpenSource/hire/blob/master/typescript_zh.md
题目是这样:
interface Action<T> {
payload?: T;
type: string;
}
class EffectModule {
count = 1;
message = "hello!";
delay(input: Promise<number>) {
return input.then(i => ({
payload: `hello ${i}!`,
type: 'delay'
}));
}
setMessage(action: Action<Date>) {
return {
payload: action.payload!.getMilliseconds(),
type: "set-message"
};
}
}
// 修改 Connect 的类型,让 connected 的类型变成预期的类型
type Connect = (module: EffectModule) => any;
const connect: Connect = m => ({
delay: (input: number) => ({
type: 'delay',
payload: `hello 2`
}),
setMessage: (input: Date) => ({
type: "set-message",
payload: input.getMilliseconds()
})
});
type Connected = {
delay(input: number): Action<string>;
setMessage(action: Date): Action<number>;
};
export const connected: Connected = connect(new EffectModule());
要求修改那个any,使其返回正确类型,而且这个类型要和connected一样。
有同学说,直接把any改成connected不就完了?要是这么简单也不会出这题。这个肯定是要你推出来,并且这个connected它的类型是EffectModule实例上的方法,里面的参数与返回还修改了。
这题怎么做呢,先一步步来,先提取出effectModule的方法,不然没法下一步。
提取class方法没有现成的,肯定不能keyof EffectModule,因为还有别的东西,怎么排除别的玩意呢?就是利用类型分发和class可以取值来做,如果是函数,那就提取,否则就不提取:
type MethodName<T> = {[F in keyof T]:T[F] extends Function ? F:never}[keyof T] type EE = MethodName<EffectModule>
这里同时利用value如果是never 则keyof就不会返回。这段其实挺有启发性,因为很多时候,都想搞个循环判断类型,然后进行选择,这就是个很好的范例。
拿到了name然后要改装方法它需要:
asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>> asyncMethod<T, U>(input: T): Action<U> syncMethod<T, U>(action: Action<T>): Action<U> syncMethod<T, U>(action: T): Action<U>
这个是题目给的,直接抄来:
type asyncMethod<T, U> = (input: Promise<T>) => Promise<Action<U>> type asyncMethodConnect<T, U> = (input: T) => Action<U> type syncMethod<T, U> = (action: Action<T>) => Action<U> type syncMethodConnect<T, U> = (action: T) => Action<U>
然后需要做一个类型分发,用来判断是哪个方法,再分发给哪个方法:
type EffectMethodAssign<T> = T extends asyncMethod<infer U, infer V>
? asyncMethodConnect<U, V>
: T extends syncMethod<infer U, infer V>
? syncMethodConnect<U, V>
: never
这段很简单,就是分发判断,泛型是用infer占位ok。
最后,修改connect,就大功告成.
type Connect = (module: EffectModule) => {
[F in MethodName<typeof module>]:EffectMethodAssign<typeof module[F]>
}
- Web 自动化测试与智能爬虫利器:PhantomJS 简介与实战
- 浅谈 Scala 中下划线的用途
- Java 多线程之 Runnable VS Thread 及其资源共享问题
- 块RAM的Verilog HDL调用
- 玩转千位分隔符输出
- DCM 模块的Verilog HDL 调用
- Python RPC 远程调用脚本之 RPyC 实践
- CRC16 编码器的Verilog HDL 实现
- macOS 0-day漏洞详情披露,可被利用完全接管系统
- SPI 接口协议的Verilog HDL 实现
- 玩转 Nginx 之:使用 Lua 扩展 Nginx 功能
- 键盘防抖
- 按键扫描接口的Verilog HDL 实现
- 病毒分析 | 一只“蜗牛”偷梁换柱,靠锁主页进行牟利
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 小数据| 描述性统计(Python/R 实现)
- ggplot2|发散性“正负”图
- R语言蒙特卡洛计算和快速傅立叶变换计算矩生成函数
- pheatmap|暴雨暂歇,“热图”来袭!!!
- 统一服务消息接口报48001错误
- ggplot2|ggpubr进行“paper”组图合并
- PostgreSQL drop table 空间不释放的问题解决
- R语言预测人口死亡率:用李·卡特模型、非线性模型进行平滑估计
- Dockerfile 指令
- Docker 构建容器Tomcat+Nginx+MySQL
- 三种动态控制SAP CRM WebClient UI assignment block显示与否的方法
- TCGA数据库中癌症名称缩写
- CloudFlare自定义节点优化网站
- 什么是SSL?为什么要为WordPress网站使用SSL?
- R语言再保险合同定价案例研究