开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情
前言
TypeScript 附带了许多用作实用程序的映射类型,最常见的包括 Omit、Partial、Readonly、Readonly、Exclude、Extract、NonNullable、ReturnType 等。下面来看看其中的两个是如何构建的。
Partial
Partial 是一种映射类型,可以将已有的类型属性转换为可选类型,并通过使用与 undefined 的联合使类型可以为空。
interface Point3D {
x: number;
y: number;
z: number;
}
type PartialPoint3D = Partial<Point3D>;
这里的 PartialPoint3D 类型实际是这样的:
type PartialPoint3D = {
x?: number;
y?: number;
z?: number;
}
当我们鼠标悬浮在 Partial 上时,就会看到它的定义:
type Partial<T> = { [P in keyof T]?: T[P] | undefined; }
下面来拆解一下这行代码:
- 使用泛型来传递目标接口 T;
- 使用
keyof T来获取 T 的所有 key。 - 通过使用
[P in keyof T]来访问并循环所有的 key; - 它通过添加 ? 使 key 成为可选的。
- 使用联合类型
T[P] | undefined使 key 的类型可以为空;
Exclude
Exclude 是一种映射类型,可让有选择地从类型中删除属性。其定义如下:
type Exclude<T, U> = T extends U ? never : T
它通过使用条件类型从 T 中排除那些可分配给 U 的类型,并且在排除的属性上返回 nerver。
type animals = 'bird' | 'cat' | 'crocodile';
type mamals = Exclude<animals, 'crocodile'>; // 'bird' | 'cat'
构建映射类型
通过上面的对 TypeScript 内置实用程序类型的原理解释,对映射类型有了更深的理解。最后,我们来构建一个自己的映射类型:Optional,它可以将原类型中指定 key 的类型置为可选的并且可以为空。
我们可以这样做:
- 将整个类型转换为 Optional
- 从该新类型中仅选择想要的属性使其成为可选的。
- 将原始类型与排除的属性连接起来。
实现代码及测试用例如下:
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
type Person = {
name: string;
surname: string;
email: string;
}
type User = Optional<Person, 'email'>;
// 现在 email 属性是可选的
type AnonymousUser = Optional<Person, 'name' | 'surname'>;
// 现在 email 和 surname 属性是可选的
注意,这里使用 K extends keyof T 来确保只能传递属于类型/接口的属性。否则,TypeScript 将在编译时抛出错误。
映射类型的一大优点就是它们的可组合性:可以组合它们来创建新的映射类型。
上面使用了已有的实用程序类型实现了我们想要的 Optional。当然,我们也可以在不使用任何其他映射类型的情况下重新创建 Optional 映射类型实用程序:
type Optional<T, K extends keyof T> =
{ [P in K]?: T[P] }
&
{ [P in Exclude<keyof T, K>]: T[P] };
上面的代码结合了两种类型:
- 第一种类型通过使用
?修饰符使 T 的所有 K 的 key 都是可选的。 - 第二种类型通过使用
Excluse<keyof T,K>来获取剩余的key。