提要
- 什么是工具泛型?
- 学习工具泛型前置条件?
- 学习官方提供的工具泛型的实现方式
- 加深回顾
- 总结
- 题目
背景
一直没有深入学习更多内容,后来加入前端团队,有辛认识到有着 TS 丰富经验的前辈,开始意识到自己在 TS 方面还有很多盲区,所以选择跳出来,往更深的内容学习。现在与大家分享工具泛型相关的学习,有什么不足的地方,希望能和大家共同交流。如果不想看的,可以直接请官网学习
官网传送门
什么是工具泛型?
处理一些类型相关的工具泛型,有些人也叫它工具类型,因为它具有一定的工具性质,就是类似纯函数的概念。
学习工具泛型前置条件?
type 类型别名
顾名思义,你可以理解成,给类型取个名字
type Person = {
name: string
age: number
}
const me: Person = {
name: 'front-end developer',
age: 66,
}
同时我们也可以用来声明工具泛型
type TPickKey<T> = keyof T
interface Person {
name: string
age: 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 = Person
type 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: string
age: number
}
const you: Person = {
name: 'yyds',
age: 18,
}
const me: Partial<Person> = {
name: 'bugaboo',
}
// typeof me = {
// name?: string
// age?: number
// }
当我在认识一个未知的东西时,我需要对它尽可能的全面了解,那么它的遍历会很深吗?
interface HobbyType {
title: string
desc: string
}
interface Person {
name: string
age: number
hobby: 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: string
age: 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<K, T>
将类型应用在联合类型上
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>
在 T 中,过滤掉非 S 的类型
interface Person {
name: string
age: 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<T, S>
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>
在T中删除对应的K
interface Person {
name: string
age: 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 }
- Extract - 取出两个类型集合的交集
// tip: 跟 Exclude 有关
type MyExtract<T, K> = // 自行脑补
type My = MyExtract<1 | 2, 2>
// type My = 2
- NonNullable - 去除null和undefined类型
// tip: 利用条件类型的联合类型自动分发特点
type MyNonNullable = // 自行脑补
type My = MyNonNullable<string | null | undefined>
// type My = string
- Parameters - 获取函数的行参结构类型
// tip: 跟 ReturnType 有关
type MyParameters<T> = {
}
type My = MyParameters<(arg: { name: string }) => string>
// type My = [arg: {
// name: string;
// }]
还有一些不怎么常用的工具,可以在官网查看学习官网传送门
总结
网上有很多相关的文章,大多都是源码结合解析,我也是如此。不过,按照我的学习路径,先总结了基本前置知识,比如infer、条件判断等,通过递进式的方式学习掌握。我没写全,因为我希望,通过我文章,总有点收获吧,可以自己把剩下的拿来练习。可能大家一开始会比较抵触,尤其是应用到实际中时,会比较考验解构的能力。
题目
- 获取类型中,某元素的类型。
eg:
interface Man {
name: string
age: number
}
typeof age = PickType<Man, 'age'>
age = number