likes
comments
collection
share

一文读懂TypeScript泛型工具类型-Partial<T>

作者站长头像
站长
· 阅读数 10

1. 定义

在 TypeScript 中,默认内置了许多泛型工具类型,我们可以使用这些工具类型,来简化 T 的操作,并且可以让我们的类型定义更加的灵活和严谨。

今天,我给大家详细介绍一下泛型工具类型 Partial<T>

泛型工具类型 Partial<T> 接收一个类型参数 T,并将该类型的所有属性设置为可选的属性

具体来说,Partial<T> 会生成一个新的类型,该类型具有与 T 相同的属性,但所有属性都变为可选。这意味着我们可以只指定部分属性的值,而不需要提供 T 的所有属性值。

2. 源码

Partial<T> 在 TypeScript 中的源码实现:

type Partial<T> = {
    [P in keyof T]?: T[P];
};

实现原理:

  • type Partial<T>:使用关键字 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<T> 的基本用法:

interface Person {
  name: string;
  age: number;
}

type PartialPerson = Partial<Person>;

上面这段代码中,定义了一个名为 Person 的接口,里面有2个必选属性 name 和 age。接着,我们使用泛型工具类型 Partial 创建一个新的类型,将 Person 中的所有属性设置为可选的属性,那么 PartialPerson 的类型将等同于下面的这段代码:

type PartialPerson = {
  name?: string;
  age?: number;
}

3. 使用场景

3.1. 当你需要在创建一个对象时,只设置部分属性,而保留其他属性的默认值。

下面我们一起来看个简单的例子:

不使用泛型工具类型 Partial<T> 的情况,将对象类型的属性变为可选属性:

如果我们想将一个对象中的所有属性都设置为可选,那么我们可以按以下的代码进行操作:

interface Person {
  name?: string;
  age?: number;
  gender?: string;
}

在上面的代码中,我们定义了一个接口 Person,有 name、age、gender 3个属性,属性名称后面加了个问号( ? ),代表该属性是可选属性。

然后我们定义几个变量,并且指定类型都为 Person,这时变量就可以不提供属性,或者提供 name、age、gender 这几个属性。

// 下面的几种写法都是正确的

// 正确:定义 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<T> 的情况,将对象类型的属性变为可选属性:

interface Person {
  name: string;
  age: number;
}

type NewPerson = Partial<Person>;

在上面的代码中,使用泛型工具类型 Partial,并传入一个类型 Person,此时 NewPerson 就拥有与 Person 相同的结构,但是里面的属性都变为可选的,此时的 NewPerson 就相当于:

type NewPerson = {
  name?: string;
  age?: number;
}

下面我们就可以定义变量并且指定类型为 NewPerson:

// 下面的几种写法都是正确的

// 正确:定义 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. 当你需要在函数参数中使用部分属性对象,而不需要传递完整对象。

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 来表示这些属性可以被省略或在运行时动态添加。

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 有个局限性,就是只支持处理第一层的属性,如果我的接口定义是下面这样子的,可以看到,第二层里面的属性就不处理了。

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 值导致的运行时错误。