提要
- 什么是工具泛型?
- 学习工具泛型前置条件?
- 学习官方提供的工具泛型的实现方式
- 加深回顾
- 总结
- 题目
背景
一直没有深入学习更多内容,后来加入观麦前端团队,有辛认识到有着 TS 丰富经验的前辈,开始意识到自己在 TS 方面还有很多盲区,所以选择跳出来,往更深的内容学习。现在与大家分享工具泛型相关的学习,有什么不足的地方,希望能和大家共同交流。如果不想看的,可以直接请官网学习
官网传送门
什么是工具泛型?
处理一些类型相关的工具泛型,有些人也叫它工具类型,因为它具有一定的工具性质,就是类似纯函数的概念。
学习工具泛型前置条件?
type 类型别名
顾名思义,你可以理解成,给类型取个名字
type Person = {name: stringage: number}const me: Person = {name: 'front-end developer',age: 66,}
同时我们也可以用来声明工具泛型
type TPickKey<T> = keyof Tinterface Person {name: stringage: number}const myKey: TPickKey<Person> = 'name'// TPickKey<Person> = 'name' | 'age'// 这个泛型主要获取传入类型的 key,并放回key的联和类型
条件类型
在TS 2.8 加入了条件类型,逻辑是三元运算符,所以比较好理解
T extends U ? X : Y
infer
声明一个变量,并对其进行使用,可以看后面的ReturnType进行理解
extends 继承
下面的一些内容会经常用extends关键字,extends类似数学集合里的子集概念,可以看一下这篇文章,会对TS有进一步对理解。深入Typescript 的类型系统
keyof
keyof 操作符是在 TypeScript 2.1 版本引入的,操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
interface Person {name: string}type Keys = keyof Person // Keys = "name"
typeof
typeof 操作符可以用来获取一个变量或对象的类型
interface Person {name: string}const me: Person = {name: 'bugaboo'}type MyType = typeof me // MyType = Persontype MyNameType = typeof me.name // MyNameType = string
in
遍历联合类型
可以查看文章中的实现机制
- (横杠)
逻辑取反,参考Required
对比文章中Partial的Required两个的代码,可以非常快速理解。
学习官方提供的工具泛型的实现方式
在编写复杂的工具泛型前,我们必须要掌握基础工具及语法,才能更好的理解和实现。
请先理解上一节的内容
首先我们来认识一些常用的命名规则,这里参考 java 的泛型命名规范
- E - Element (used extensively by the Java Collections Framename)
- K - Key
- N - Number
- T - Type
- V - Value
- S, U, V etc. - 2nd, 3rd, 4th types
- R - Result
- A - Accumulator
Partial
把传入的类型,变为可选的
interface Person {name: stringage: number}const you: Person = {name: 'yyds',age: 18,}const me: Partial<Person> = {name: 'bugaboo',}// typeof me = {// name?: string// age?: number// }
当我在认识一个未知的东西时,我需要对它尽可能的全面了解,那么它的遍历会很深吗?
interface HobbyType {title: stringdesc: string}interface Person {name: stringage: numberhobby: HobbyType[]}const you: Person = {name: 'yyds',age: 18,hobby: [{ title: 'play game', desc: 'many games' }]}const me: Partial<Person> = {name: 'bugaboo',hobby: [{}] // 类型“{}”缺少类型“HobbyType”中的以下属性: title, descts(2739)}
实现
type MyPartial<T> = {[K in keyof T]?: T[K]}type MyType = MyPartial<{name: stringage: number}>
解析
- (
type MyPartial<T>) —- 首先,我们声明个工具泛型 - (
K in keyof T,这里K代表着T类型中的key) —- 遍历T - (
T[K]) —- 获取对应key的类型 - (
?:) —- 设置成可选
Readonly
把传入的类型变为只读状态
interface Person {name: string}let me: Readonly<Person> = {name: 'bugaboo'}me.name = 'yyds' // 无法分配到 "name" ,因为它是只读属性。ts(2540)
实现
type MyReadonly<T> = {readonly [K in keyof T]: T[K]}type MyType = MyReadonly<{name: string}>
解析
- (
type MyReadonly<T>) —- 首先,我们声明个工具泛型 - (
K in keyof T,这里K代表着T类型中的key) —- 遍历T - (
T[K]) —- 获取对应key的类型 - (
readonly) —- 设置成只读
Record
将类型应用在联合类型上
interface Person {name: string}type Peoples = Record<'汉族' | '少数民族', Person>// type Peoples = {// 汉族: Person// 少数民族: Person// }
实现。
type MyRecord<K extends keyof any, T> = {[S in K]: T}type MyType = MyRecord<'汉族' | '少数名族', { name: '' }>
解析
- (
type MyRecord) —- 首先,我们声明个工具泛型 - (
<K extends keyof any, T>) —- 传入两个参 - (
K extends keyof any) —-K继承any,就是随便,爱传啥传啥,看上面我例子,我都传中文了。 - (
S in K) —- 遍历K
Pick
在 T 中,过滤掉非 S 的类型
interface Person {name: stringage: number}type MyType = Pick<Person, 'name'>// type MyType = {// name: string// }
实现
interface Person {name: string}type MyPick<T, S extends keyof T> = {[K in S]: T[K]}type MyType = MyPick<Person, 'name'>
解析
- (
type MyPick) —- 首先,我们声明个工具泛型 - (
<T, S extends keyof T>) —- 传入两个参 - (
S extends keyof T) —- 之前聊到keyof是获取联合类型,所以S是继承T的key值组成的联合类型。 - (
K in S) —- 遍历S - (
T[K]) —- 获取T中对应的类型
Exclude
type T = Exclude<1 | 2, 2 | 3>// type T = 1
实现
type MyExclude<T, S> = T extends S ? never : T
解析
利用条件类型,判断T是否属于S的子集,然后返回never或T
对于联合类型会自动分发条件
type T = Exclude<1 | 2, 2 | 3>(1 extends 2 | 3 ? : 2 | 3) | (2 extends 2 | 3 ? never : 2 | 3)
所以在传入联合类型时,会返回到两个类型集合的补集
Omit
在T中删除对应的K
interface Person {name: stringage: number}type Me = Omit<Person, 'name'>// type Me = { age: number }
实现
type MyOmit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
解析
利用上面的 Pick 和 Exclude进行组合
- 先找出补集的
keys - 然后从类型集合里,挑它出来。
ReturnType
获取函数返回值的类型
function foo(a: string): Array<string> {return [a]}type FooReturnType = ReturnType<typeof foo>// type FooReturnType = string[]
实现
type MyReturnType<T> = T extends (...arg: any[]) => infer R ? R : any
解析
利用infer代指函数的返回类型,然后通过 extends 判断是否是函数类型,再返回对应的infer代指的类型。
加深回顾
其实还没写全,我只把几个比较有特点的工具写了。剩下的,不过是基于类似的实现,稍微改动来一下。我列出来,大家可以尝试一下,自己实现。
Required - 把所有属性变为必选
// tip: 跟 Partial 有关type MyRequired<T> = // 请自行脑补type My = MyRequired<{ name?: string, age: number }>// type My = { name: string }
参考
type MyRequired<T> = {[K in keyof T]-?: T[K]}
Extract - 取出两个类型集合的交集
// tip: 跟 Exclude 有关type MyExtract<T, K> = // 自行脑补type My = MyExtract<1 | 2, 2>// type My = 2
参考
type MyExtract<T, K> = T extends K ? T : never
NonNullable - 去除null和undefined类型
// tip: 利用条件类型的联合类型自动分发特点type MyNonNullable = // 自行脑补type My = MyNonNullable<string | null | undefined>// type My = string
参考
type MyNonNullable<T> = T extends null | undefined ? never : T
Parameters - 获取函数的行参结构类型
// tip: 跟 ReturnType 有关type MyParameters<T> = {}type My = MyParameters<(arg: { name: string }) => string>// type My = [arg: {// name: string;// }]
还有一些不怎么常用的工具,可以在官网查看学习官网传送门
总结
网上有很多相关的文章,大多都是源码结合解析,我也是如此。不过,按照我的学习路径,先总结了基本前置知识,比如infer、条件判断等,通过递进式的方式学习掌握。我没写全,因为我希望,通过我文章,总有点收获吧,可以自己把剩下的拿来练习。可能大家一开始会比较抵触,尤其是应用到实际中时,会比较考验解构的能力。
