TypeScript 实用程序中的映射

80 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天,点击查看活动详情

前言

TypeScript 附带了许多用作实用程序的映射类型,最常见的包括 OmitPartialReadonlyReadonlyExcludeExtractNonNullableReturnType 等。下面来看看其中的两个是如何构建的。

Partial

Partial 是一种映射类型,可以将已有的类型属性转换为可选类型,并通过使用与 undefined 的联合使类型可以为空。

interface Point3D {
    xnumber;
    ynumber;
    znumber;
}

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 = {
  namestring;
  surnamestring;
  emailstring;
}
  
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。