TS官方工具类型

TypeScript 中有很多预置的工具类型,它们无需导入,可在全局使用。接下来我们来了解一下这些基本的工具类型,一方面的目的是为了了解如何使用基础类型来实现这些工具类型,另一方面也能过利用这些工具类型来应对不同的业务场景,实现更复杂的类型。

Omit
它的作用是通过第二个参数去掉指定的key并且返回新的类型,下面举个🌰

1
2
3
4
5
6
7
8
9
10
11
type Omit<T, K extends keyof T> = {[P in keyof T as P extends K ? never: P] :T[P]}

interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = Omit<Todo, 'description' | 'title'>
const todo: TodoPreview = {
completed: false,
}

在上述示例中,Omit 类型的实现使用了前面介绍的 Pick 类型。我们知道 Pick 类型的作用是选取给定类型的指定属性,那么这里的 Omit 的作用应该是选取除了指定属性之外的属性,而 Exclude 工具类型的作用就是从入参 T 属性的联合类型中排除入参 K 指定的若干属性。

Pick
它可以通过第二个参数来指定新的类型中包含哪些key值,并且返回这个新的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};

interface Todo {
title: string
description: string
completed: boolean
}
type TodoPreview = Pick<Todo, 'title' | 'completed'>
const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}

我们可以发现,Pick这个工具类型接收的两个参数均为泛型,第一个 T 为原有的参数类型,而第二个参数K为需要提取的键值 key。
当有了Pick以后,Omit也可以这样来实现,如下。

1
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

Readonly
Readonly,顾名思义为将指定类型的属性全部设置为只读,这也表明返回的新的类型的属性不可以再被二次赋值。🌰如下

1
2
3
4
5
6
7
8
9
10
11
12
13
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

interface Todo {
title: string
description: string
}
const todo: Readonly<Todo> = {
title: "Hey",
description: "foobar"
}
todo.title = "Hello" // Error: cannot reassign a readonly property

在上述示例中,经过 Readonly 处理后,todo 的 title、description属性都变成了 readonly 只读。

Partial
Partial 可以将一个类型的所有属性变为可选的,且返回的类型是给定类型的所有子集。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Partial<T> = {
[P in keyof T]?: T[P];
};
interface Person {
name: string;
age?: number;
weight?: number;
}
type PartialPerson = Partial<Person>;
interface PartialPerson {
name?: string;
age?: number;
weight?: number;
}

在上述示例中,我们使用映射类型取出了传入类型的所有键值,并将其值设定为可选的。

Required
Required 工具类型将入参类型中的所有属性改为必填。🌰如下

1
2
3
4
5
6
7
8
9
type Required<T> = {
[P in keyof T]-?: T[P];
};
type RequiredPerson = Required<Person>;
interface RequiredPerson {
name: string;
age: number;
weight: number;
}

在上述示例中,我们在键值的后面采用 - 符号,- 与 ? 一起表示去除该类型的可选属性,因此传入类型的所有属性都变为了必填。

Exclude
上述在Omit的第二种实现方法里,我们使用了 Exclude 。不难看出了 Exclude的作用是从类型中去除指定的属性。

1
2
3
4
5
6
7
 type Exclude<T, U> = T extends U ? never : T;
type T = Exclude<'a' | 'b' | 'c', 'a'>; // => 'b' | 'c'
type NewPerson = Omit<Person, 'weight'>;
// 相当于
type NewPerson = Pick<Person, Exclude<keyof Person, 'weight'>>;
// 其中
type ExcludeKeys = Exclude<keyof Person, 'weight'>; // => 'name' | 'age'

在上述示例中,Exclude 的实现使用了条件类型。如果类型 T 可被分配给类型 U ,则不返回类型 T,否则返回此类型 T ,这样我们就从联合类型中去除了指定的类型。

Record
Record 的作用是生成接口类型,然后我们使用传入的泛型参数分别作为接口类型的属性和值。

1
2
3
4
5
6
7
8
9
10
11
12
13
type Record<K extends keyof any, T> = {
[P in K]: T;
};
type MenuKey = 'home' | 'about' | 'more';
interface Menu {
label: string;
hidden?: boolean;
}
const menus: Record<MenuKey, Menu> = {
about: { label: '关于' },
home: { label: '主页' },
more: { label: '更多', hidden: true },
};

Record的内部定义,接收两个泛型参数,泛型K即为第一个参数。p in xx意思就是遍历,如上将类型MenuKey进行遍历,也就是string。每个属性都是传入的T类型。
在上述示例中,Record 类型接收了两个泛型参数:第一个参数作为接口类型的属性,第二个参数作为接口类型的属性值。
需要注意:这里的实现限定了第一个泛型参数继承自keyof any。
在 TypeScript 中,keyof any 指代可以作为对象键的属性,如下示例:
type T = keyof any; // => string | number | symbol
说明:目前,JavaScript 仅支持string、number、symbol作为对象的键值。