《Effective TypeScript 》-- 条款14: 使用类型操作和泛型避免重复工作

70 阅读3分钟

条款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];

总结:

  1. 不要重复,不要重复,不要重复!!!
  2. 可以使用上述的方法避免重复;