1. 定义
在 TypeScript 中,默认内置了许多泛型工具类型,我们可以使用这些工具类型,来简化 T 的操作,并且可以让我们的类型定义更加的灵活和严谨。
今天,我给大家详细介绍一下泛型工具类型 Partial。
泛型工具类型 Partial 接收一个类型参数 T ,并将该类型的所有属性设置为可选的属性。
具体来说,Partial 会生成一个新的类型,该类型具有与 T 相同的属性,但所有属性都变为可选。这意味着我们可以只指定部分属性的值,而不需要提供 T 的所有属性值。
2. 源码
Partial 在 TypeScript 中的源码实现:
ini
type Partial<T> = {
[P in keyof T]?: T[P];
};
实现原理:
- type Partial 使用关键字 type 定义一个类型别名 Partial ,它接收泛型 T 作为参数。
- keyof T 通过 keyof 操作符获取泛型 T 中的所有 key(可以理解成获取对象中的属性名),它返回的是由所有属性名组成的联合类型。
- in 用于遍历泛型 T 中的每个属性名。
- T[P] 获取泛型 T 中 P 的类型。(可以理解成 JS 中访问对象属性值的方式)。
- ? 将属性名设置为可选类型。
- { [P in keyof T]?: T[P] } 这是一个映射类型的语法,通过遍历 keyof T 返回的联合类型,然后使用变量 P 来接收,P 就相当于对象中的 key,然后在 key 后面加上问号(?),表示属性是可选属性,每一次遍历返回的值为 T[P]。
下面我们先看下 Partial 的基本用法:
ini
interface Person {
name: string;
age: number;
}
type PartialPerson = Partial<Person>;
上面这段代码中,定义了一个名为 Person 的接口,里面有2个必选属性 name 和 age。接着,我们使用泛型工具类型 Partial 创建一个新的类型,将 Person 中的所有属性设置为可选的属性,那么 PartialPerson 的类型将等同于下面的这段代码:
ini
type PartialPerson = {
name?: string;
age?: number;
}
3. 使用场景
3.1. 当你需要在创建一个对象时,只设置部分属性,而保留其他属性的默认值。
下面我们一起来看个简单的例子:
不使用泛型工具类型 Partial 的情况,将对象类型的属性变为可选属性:
如果我们想将一个对象中的所有属性都设置为可选,那么我们可以按以下的代码进行操作:
typescript
interface Person {
name?: string;
age?: number;
gender?: string;
}
在上面的代码中,我们定义了一个接口 Person,有 name、age、gender 3个属性,属性名称后面加了个问号( ? ),代表该属性是可选属性。
然后我们定义几个变量,并且指定类型都为 Person,这时变量就可以不提供属性,或者提供 name、age、gender 这几个属性。
csharp
// 下面的几种写法都是正确的
// 正确:定义 user 变量,类型为 Person,name 和 age 和 gender 属性都可以不传
let user: Person = {}
// 正确:定义 user1 变量,类型为 Person,可以只传 name 属性
let user1: Person = {
name: "Echo",
}
// 正确:定义 user2 变量,类型为 Person,可以只传 name 和 age 属性
let user2: Person = {
name: "James",
age: 36,
}
// 正确:定义 user3 变量,类型为 Person,可以同时传 name 和 age 和 gender 属性
let user3: Person = {
name: "Steven",
age: 33,
gender: "Male",
}
使用泛型工具类型 Partial 的情况,将对象类型的属性变为可选属性:
ini
interface Person {
name: string;
age: number;
}
type NewPerson = Partial<Person>;
在上面的代码中,使用泛型工具类型 Partial,并传入一个类型 Person,此时 NewPerson 就拥有与 Person 相同的结构,但是里面的属性都变为可选的,此时的 NewPerson 就相当于:
ini
type NewPerson = {
name?: string;
age?: number;
}
下面我们就可以定义变量并且指定类型为 NewPerson:
arduino
// 下面的几种写法都是正确的
// 正确:定义 user 变量,类型为 NewPerson,name 和 age 属性都可以不传
const user: NewPerson = {}
// 正确:定义 user1 变量,类型为 NewPerson,只传 name 属性
const user1: NewPerson = {
name: 'Echo',
}
// 正确:定义 user2 变量,类型为 NewPerson,同时传 name 和 age 属性
const user2: NewPerson = {
name: 'Echo',
age: 26
}
// 下面这种写法是错误的
// 报错:对象字面量只能指定已知属性,并且"address"不在类型"Partial<Person>"中。
const user3: NewPerson = {
name: 'Echo',
age: 26,
address: 'GuangZhou',
}
3.2. 当你需要在函数参数中使用部分属性对象,而不需要传递完整对象。
scss
interface User {
id: number;
name: string;
age: number;
}
function updateUser(user: Partial<User>): void {
// 在这里,user 参数的类型是 Partial<User>
// 这意味着可以只传递类型 User 的部分属性,而不是整个对象
// 操作用户属性
if (user.id) {
// 操作 id
}
if (user.name) {
// 操作 name
}
if (user.age) {
// 操作 age
}
}
// 函数参数中使用 Partial 对象
updateUser({ name: "Echo" }); // 这里只设置了 name 属性,其他属性保留默认值或未设置
3.3. 动态对象属性
当需要处理具有可选或动态属性的对象时,可以使用 Partial 来表示这些属性可以被省略或在运行时动态添加。
typescript
function processObject(obj: Partial<{ [key: string]: number }>): void {
// 处理动态对象的属性
for (const key in obj) {
console.log(key, obj[key]); // 打印出:prop1, 10 prop2, 20
}
}
const dynamicObject: Partial<{ [key: string]: number }> = {
prop1: 10,
prop2: 20
};
processObject(dynamicObject);
4. 注意事项
Partial 有个局限性,就是只支持处理第一层的属性,如果我的接口定义是下面这样子的,可以看到,第二层里面的属性就不处理了。
ini
interface Person {
name: string;
age: number;
child: {
name: string;
address: string;
phone: number;
}
}
type NewPerson = Partial<Person>;
// 等同于
/*
type NewPerson = {
name?: string | undefined;
age?: number | undefined;
child?: {
name: string;
address: string;
phone: number;
} | undefined;
}
*/
// 报错:类型"{ name: string; }"缺少类型"{ name: string; address: string; phone: number; }"中的以下属性: address, phone
const user: NewPerson = {
name: 'Echo',
child: {
name: 'James',
}
}
需要注意的是:使用 Partial 后,属性的值可以是 undefined。因此在使用属性时,需要进行空值检查,以防止使用 undefined 值导致的运行时错误。