TypeScript中是如何将泛型T中的属性都变为可选属性的?Partial源码分析

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

相关推荐
黑客老陈13 分钟前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
运维·服务器·前端·网络·安全·web3·xss
正小安18 分钟前
Vite系列课程 | 11. Vite 配置文件中 CSS 配置(Modules 模块化篇)
前端·vite
暴富的Tdy1 小时前
【CryptoJS库AES加密】
前端·javascript·vue.js
neeef_se1 小时前
Vue中使用a标签下载静态资源文件(比如excel、pdf等),纯前端操作
前端·vue.js·excel
m0_748235611 小时前
web 渗透学习指南——初学者防入狱篇
前端
z千鑫1 小时前
【前端】入门指南:Vue中使用Node.js进行数据库CRUD操作的详细步骤
前端·vue.js·node.js
m0_748250742 小时前
Web入门常用标签、属性、属性值
前端
m0_748230442 小时前
SSE(Server-Sent Events)返回n ,前端接收数据时被错误的截断【如何避免SSE消息中的换行符或回车符被解释为事件消息的结束】
前端
生产队队长3 小时前
项目练习:element-ui的valid表单验证功能用法
前端·vue.js·ui