原来Partial这么神!TS中的对象可选属性解决方案

阅读时间约 5 分钟。


type User = {
  id: number;
  name: string;
  age: number;
}

想必你对上面的类型定义并不陌生。类型 User 有三个成员:idnameage,这三个成员属性都是必选的。

假设你要创建一个用户,但是创建的新用户信息中没有 age 字段,那你该怎么办?再创建一个新的类型定义 AnotherUser 吗?

这样写的话也太费劲了吧~ 不仅麻烦、还会产生许多冗余代码,可读性和可维护性也都有些差强人意。

那么能不能在 User 类型的基础上满足我们的需求呢?答案是可以的,通过 TS 标准库中的 Partial 类型就能完美解决这个问题。

通过本文的学习,你将有能力彻底解决类似的问题,并且你还能学到 Partial 源码的具体实现,助你写出更加强大、灵活的类型定义。

1. Partial 的定义和用法

Partial 是 TS 中的一个工具类型(Utility Type),它的作用只有一个:将一个对象类型中的所有属性变为可选属性。换句话说,Partial 接受一个泛型参数 T,并返回一个新的类型,新类型与 T 相同,但是 T 类型中的所有属性都变为可选属性。

以本文开头的 User 类型定义为例,我们将该类型作为参数传给 Partial

type UpdatedPerson = Partial<User>;

得到的 UpdatedPerson 类型与下面的类型定义是相同的:

type UpdatedPerson = {
  id?: number;
  name?: string;
  age?: number;
}

由此可见,Partial 将泛型 T 中的全部属性都转为了可选属性。这样,我们就不用再专门定义额外的类型了。

那么,Partial 的这个能力是怎么实现的呢?下面,我们就来看一下它的源码。

2. Partial 源码分析

Partial 的源码看起来非常简单,只有几行代码:

/**
 * Make all properties in T optional
 */
type Partial<T> = {
  [P in keyof T]?: T[P];
};

细心的你应该发现了,在属性名和属性值之间有个 ?,这个 ? 就表示“可选”的意思。

而难点是 [P in keyof T] 部分,我们应该怎么理解这部分内容呢?

这里面有两个重要的部分:inkeyof。他们在 TS 标准库的源码中频繁出现,理解了他们,我们编写 TS 的能力将提高一个台阶。

这段源码使用关键字 in 和操作符 keyof 定义了一个映射类型。keyof 是 TS 中的索引类型查询操作符,用于获取一个对象类型的所有属性名称的联合类型。我们通过一个例子来演示一下:

interface User {
  name: string;
  age: number;
  gender: string;
}

type UnionType = keyof User;

User 是一个对象类型,keyof 会获取 User 对象类型的属性名称联合类型,也就是 "name" | "age" | "gender"。UnionType 的类型与下面的类型定义是等价的:

type UnionType = "name" | "age" | "gender";

这里要注意的是,keyof 操作符只能用于对象类型。除对象类型外的其他类型,keyof 是无法使用的。例如:

type MyNumber = number;
type MyNumberKey = keyof MyNumber; // 编译错误:Type 'number' has no matching index signature for type 'keyof number'

我们再来看源码中的 in 关键字,这个关键字的作用是用于映射类型的。在这里 in 表示的是迭代联合类型中的每个属性,而泛型 P 就表示这些属性。

这样,Partial 类型中的成员就与传入的泛型 T 中的成员一致了。属性名称后的 ? 表示可选类型,属性值则是取的泛型 T 中的属性值。由此,便将泛型 T 中的属性转变成了可选属性。

3. 总结

不难发现,使用 Partial 可以让我们方便的定义可选属性。在 Partial 源码中,我们重点分析了关键字 in 和操作符 keyof。除了 Partial 类型外,TS 标准库中 Required 和 Readonly 也是通过 in 和 keyof 来实现的,他们的源码也很简短,感兴趣的可以自行阅读分析。下面是这两个类型的源码:

/**
 * Make all properties in T required
 */
type Required<T> = {
  [P in keyof T]-?: T[P]; // `-?` 符号是一个操作符,用于将属性变为必需的,即必须存在并且不能为 undefined 或 null。
};

/**
 * Make all properties in T readonly
 */
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

熟练运用关键字 in 和操作符 keyof 能让你写出更加灵活、强大的类型定义。后期我们将继续分析 TS 标准库中的其他工具类型,助你从“源头”彻底掌握 TS。


本文首发微信公众号码上花甲,欢迎关注。

你可能感兴趣的:(滕青山的收藏夹,java)