写在前面
上一篇文章简单介绍了泛型的概念,以及函数泛型、类泛型、接口泛型的使用方式,还有多参数泛型和泛型约束的方法。本篇将介绍下ts官网提供的一些工具泛型,什么是工具泛型呢?简单来说就是可以把一个复杂的类型转变成我们想要的类型。以下代码举例
interface StudentInfos { name: string age: number tall: number weight: number favor: string[] sex: 'boy' | 'girl' country: string id: string mother: string father: string }
这是一个学生信息的接口,但是有些业务只关心tall和weight,有一些业务又只关心mother和father,于是乎我们又增加了两个接口
interface StudentBodyInfos { tall: number weight: number } interface StudentParentsInfos { mother: string father: string }
如果又有其他的业务既关心学生tall和weight,又关心mother和father,那么又会增加一些接口,如此一来,项目中管理各种各样重复的接口类型也是一笔不小的维护开销,如何做到灵活处理呢?那么活用工具泛型无疑可以大大省去这些开销,比如使用Pick
const studentBodyInfo:Pick<StudentInfos, 'tall' | 'weight'> = { tall: 165, weight: 120, } const studentParentsInfo:Pick<StudentInfos, 'mother' | 'father'> = { mother: 'mother', father: 'father', }
这里的Pick也是工具泛型的一种,可以从一个复杂的类型中选择需要的属性返回一个新类型。相比起遇到细粒度的类型重复去新建interface,使用Pick维护简单,而且不需要管理诸多的Interface。
Pick实现
/** * From T, pick a set of properties whose keys are in the union K */ type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
这里要讲3个点,不然后面的工具泛型也看不懂
- in 遍历联合类型
- keyof指代遍历出类型的key 返回联合类型
type StudentInfoKeys = keyof StudentInfos; // 等同以下写法 type StudentInfoKeys = "name" | "age" | "tall" | "weight" | "favor" | "sex" | "country" | "id" | "mother" | "father"
- extends在ts用法很多,可以泛型约束,类/接口继承,笔者认为把这里理解泛型约束,要求我们传入的必须是StudentInfoKeys的子集。此处再补充一点extends也可以拿来做条件判断。
T extends U ? X : Y // 如果T包含的类型 是U包含的类型的 '子集',那么取结果X,否则取结果Y
总结Pick的用处
- 给定一个复杂的类/接口T
- 约束传入的key的联合类型K属于T所有keys的联合类型的子集
- 遍历K,并要求遍历中的P类型为T中P的类型
还有很多的工具类型,大体上都是通过in keyof extends这些进行设计,下面来看看都有哪些实用的工具泛型吧。
Partial
让一个类型所有的props可选,适用于一些对类型结构需求不明确,需要灵活处理的场景。
type Partial<T> = { [P in keyof T]?: T[P]; }; const someStudentsInfo: Partial<StudentInfos> = { tall: 165, weight: 120, mother: 'mother' }
PickWithPartial
小插曲,我们想让Pick返回的类型也props可选,参照Partial得出我们新造出来的新工具泛型
type PickWithPartial<T, K extends keyof T> = { [P in K]?: T[P]; }; const someStudentBodyInfo:PickWithPartial<StudentInfos, 'tall' | 'weight'> = { tall: 165 }
Required
前有Partial,相反地想让所有的属性必选,怎么处理呢?这里要说下 – 的用法。
- -表示逻辑取反 比如 -? 表示 非可选。
那么Required就很简单了
type Required<T> = { [P in keyof T]-?: T[P]; }; interface PartProps { a?: string b?: string } // 因为Required,所以要求a, b属性必选 const a:Required<PartProps> = { a: 'a', b: 'b' }
Readonly
所有属性只读
type Readonly<T> = { readonly [P in keyof T]: T[P]; };
Record
由联合类型的key当做新类型的key
type Record<K extends keyof any, T> = { [P in K]: T; }; type RecordKeys = 'a' | 1; const a : Record<RecordKeys, string> = { a: 'a', 1: 'a' } // 更合理的场景 type StudentsName = '小明' | '小张' | '小王' const studentsInfo : Record<StudentsName, StudentInfos> = { '小张': {} as StudentInfos, '小明': {} as StudentInfos, '小王': {} as StudentInfos, }
Exclude
从一个联合类型中排除掉属于另一个联合类型的子集
type Exclude<T, U> = T extends U ? never : T; type name = Exclude<'小明' | '小张', '小张' | '小王'> // 等同 type name = '小明'
Extract
跟Exclude相反,从从一个联合类型中取出属于另一个联合类型的子集
type Extract<T, U> = T extends U ? T : never; type name = Exclude<'小明' | '小张', '小张' | '小王'> // 等同 type name = '小张'
Omit
跟Pick相反,把选出的排除掉
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
- Exclude<keyof T, K> 排除掉属于K的子集
- Pick<T, Exclude<keyof T, K>> 选出被排除掉的剩下的props
NonNullable
排除null/undefined类型
type NonNullable<T> = T extends null | undefined ? never : T; type studentName = NonNullable<string | null | undefined> // string
ReturnType
返回函数的返回结果类型
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any; type resType = ReturnType<(arg: { name: string }) => string> // 等同于 type resType = string;
这里一个新字眼infer,作用是命名变量,这里可以理解成把返回结果类型通过Infer命名成R
自由构建
ts工具泛型非常多且实用,大多通过in keyof extends infer -这些关键字进行构筑,理解了这些才能自由自在的构筑自己的工具泛型。比如有个需求,通过调取函数获得班级学生的姓名,再通过姓名构建key的类型映射。
type StudentName = '小张' | '小明' | '小王'; type StudentRizeler<T extends () => StudentName, K> = T extends (...args: any) => StudentName ? Record<StudentName, K> : any; const demo: StudentRizeler<() => StudentName, StudentInfos> = { '小张': {} as StudentInfos, '小明': {} as StudentInfos, '小王': {} as StudentInfos, } const demo2: StudentRizeler<() => StudentName, Pick<StudentInfos, 'age' | 'favor'>> = { '小张': { age: 25, favor: ['eat'] }, '小明': { age: 26, favor: ['sleep'] }, '小王': { age: 15, favor: ['play'] }, }
总结
ts不仅提供的强大的类型系统,外加提供的开放能力也允许开发者自由的构建各种各样的类型。工具泛型在提高代码灵活性降低管理interface的同时,自定义的一些工具泛型也是一抹亮丽的风景啊~