原来Partial这么神!TS中的对象可选属性解决方案
阅读时间约 5 分钟。
type User = {
id: number;
name: string;
age: number;
}
想必你对上面的类型定义并不陌生。类型 User
有三个成员:id
、name
和 age
,这三个成员属性都是必选的。
假设你要创建一个用户,但是创建的新用户信息中没有 age
字段,那你该怎么办?再创建一个新的类型定义 AnotherUser
吗?
这样写的话也太费劲了吧~ 不仅麻烦、还会产生许多冗余代码,可读性和可维护性也都有些差强人意。
那么能不能在 User
类型的基础上满足我们的需求呢?答案是可以的,通过 TS 标准库中的 Partial 类型就能完美解决这个问题。
通过本文的学习,你将有能力彻底解决类似的问题,并且你还能学到 Partial 源码的具体实现,助你写出更加强大、灵活的类型定义。
1. Partial
的定义和用法
Partial
是 TS 中的一个工具类型(Utility Type),它的作用只有一个:将一个对象类型中的所有属性变为可选属性。换句话说,Partial<T>
接受一个泛型参数 T
,并返回一个新的类型,新类型与 T 相同,但是 T
类型中的所有属性都变为可选属性。
以本文开头的 User
类型定义为例,我们将该类型作为参数传给 Partial
:
type UpdatedPerson = Partial<User>;
得到的 UpdatedPerson
类型与下面的类型定义是相同的:
type UpdatedPerson = {
id?: number;
name?: string;
age?: number;
}
由此可见,Partial<T>
将泛型 T
中的全部属性都转为了可选属性。这样,我们就不用再专门定义额外的类型了。
那么,Partial
的这个能力是怎么实现的呢?下面,我们就来看一下它的源码。
2. Partial
源码分析
Partial
的源码看起来非常简单,只有几行代码:
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
细心的你应该发现了,在属性名和属性值之间有个 ?
,这个 ?
就表示“可选”的意思。
而难点是 [P in keyof T]
部分,我们应该怎么理解这部分内容呢?
这里面有两个重要的部分:in
和 keyof
。他们在 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。
转载自:https://juejin.cn/post/7224927106130739255