条款14: 使用类型操作和泛型避免重复工作
DRY原则:不要重复自己 don't repeat yourself;在软件开发中能找到的最接近于通用的建议;
在类型中,也有很多方法能够让我们避免重复的工作,所以这一章我们会学习如何在类型之间进行映射;
1. 给类型命名,拿函数而言,就是生成一个完成的函数的类型,包括入参和出参,而不是分别定义出参和入参;
function distance(a: { x: number; y: number }, b: { x: number; y: number }): number {
return ...
}
// 更好的方式
interface Point2D {
x: number;
y: number;
}
function distance(a: Point2D, b: Point2D): number {}
2. 利用类型扩展消除重复
1. extends 关键字
类型之间是可以有继承关系的,我们可以通过抽象出类型之间公共的基类来消除代码的重复
interface Person {
firstName: string;
lastName: string;
}
interface PersonWithBirthDate extends Person {
birthDay: Date;
}
2. 利用类型操作符进行复用
还是拿上述的例子举例,
type TPersonWithBirthDate = Person & { birth: Date };
但是使用类型操作符 & 生成的联合类型是不允许被继承的,如果你想要让类型仍然可以被继承的时候,使用上面的方式进行编码;
3. 复用已有类型,即取已有类型的一部分
// 代码举例
interface State {
id: string;
title: string;
files: string[];
contents: string;
}
interface TopNavState {
id: string;
title: string;
files: string[];
}
我们可以看到 TopNaState 和 State 其实存在很多重复,我们不如将前者定义为后者的子集,然后保持一个单一的接口就能定义整个应用程序的状态;
// 通过类型索引进行类型重复
type TopNavState = {
id: State['id'];
title: State['title']
files: State['files']
}
但是这么写对于 State 我们又写了三次,而且属性名也都是重复的,所以我们可以使用遍历来完成上面的操作
type Topxx = {
[k in 'id' | 'title' | 'files']: State[k]
}
这种映射类型相当于类型系统对于数组字段进行循环,所以我们能够看到,TS 将这种模式定义到其标准库中
type Pick<T, K extends keyof T> = {
[k in K]: T[K]
}
// 所以上面的类型定义可以改写为
type Topxx = Pick<State, 'id' | 'title' | 'files'>
4. 抽象标签联合类型的标签
interface SaveAction {
type: 'save';
...
}
interface UpdateAction {
type: 'update';
...
}
type Action = SAveAction | UpdateAction;
// 我们可能会直接定义类型,但是这是重复的定义,我们可以复用 Action 类型
type TType = 'save' | 'update';
// 方式1:利用联合类型的索引,可以直接获取
type TType = Action['type']; // save | update
// 方式2:利用Pick函数
type TType = Pick<Action, 'type'>; // { type: 'save' | 'upload'}
5. 我们在初始化一个类型之后会继续更新时,更新的类方法可能会存在大量的重复
interface Options {
width: number;
height: number;
color: string;
}
interface UpdateOptions {
width?: number;
height?: number;
color?: string;
}
// 此时,我们可以使用一个映射类型和 keyof 关键字来构造类型
type TUpdateOptions = {
[k in keyof Options]?: Options[k] // 类型同上
}
// keyof 可以可以获取某个类型的 key 值 组成联合类型;
上面的方式也被纳入到标准库,叫做 Partial,我们可以使用
type TUpdateOptions = Partial<Options>
6. 当我们想要定义一个与值类型相同的类型
我们可以使用 typeof 关键词,typeof 不可以操作类型,只可以操作 JS 的变量,返回变量的类型;
7. 为一个函数或者方法的推断返回值创建一个类型
使用 ReturnType,要注意的是 ReturnType 作用在函数的类型 typeof fn 上,而不是作用在值空间中,不能混淆
type TInfo = ReturnType<typeof fn>
8. 泛型和如何约束泛型
泛型相当于类型的函数,我们可以通过类型系统来约束函数映射的值;
-- 使用 extends 关键字
interface Name {
first: string;
last: string;
}
type D<T extends Name> = [T, T];
总结:
- 不要重复,不要重复,不要重复!!!
- 可以使用上述的方法避免重复;