写在前面
上一篇文章简单介绍了泛型的概念,以及函数泛型、类泛型、接口泛型的使用方式,还有多参数泛型和泛型约束的方法。本篇将介绍下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的同时,自定义的一些工具泛型也是一抹亮丽的风景啊~