1. 定义
在 TypeScript 中,泛型工具类型 Pick 主要用于从 Type 中选择一组属性来构造成一个新的类型。
换句话说,就是从一个复合的类型中,取出几个想要的类型,创建出一个新的类型。
Pick 工具类型有2个类型参数 T 和 K,其中:
- T 表示的是:选择哪个对象。
- K 表示的是:选择对象中已有的哪些属性,可以选择一个也可以选择多个,选择多个属性时采用"联合类型"的写法。
2. 源码
泛型工具类型 Pick 在 TypeScript 中的源码实现:
scala
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
实现原理:
- type Pick<T, K extends keyof T>: 使用关键字 type 定义一个类型别名 Pick ,它接收两个泛型参数 T 和 K。其中,第二个参数 K extends keyof T 表示的是泛型参数 K 必须是泛型 T 的键(属性名)联合类型中的一部分。
- keyof T: 通过 keyof 操作符获取泛型 T 中的所有 key(可以理解成获取对象中的属性名),它返回的是由所有属性名组成的联合类型。
- in: 用于遍历泛型 K 中的每个属性名。
- T[P]: 获取泛型 T 中 P 的类型。(可以理解成 JS 中访问对象属性值的方式)。
- { [P in K]: T[P] }: 这是一个映射类型的语法,遍历 K 中的每个属性,然后使用变量 P 来接收,P 就相当于对象中的 key,每一次遍历返回的值为 T[P]。
- 整段代码的含义就是:从类型 T 中选择一部分属性,创建一个新的类型,该类型具有选择的属性和对应属性值的类型。
下面我们先看下 Pick<T, K> 的基本用法:
ini
interface Person {
id: number;
name: string;
age: number;
gender: string;
phone: number;
address: string;
}
type PickPerson = Pick<Person, "name" | "age">
上面这段代码中,定义了一个名为 Person 的接口,里面有6个必选属性 id、name、age、gender、phone、address。接着,我们使用泛型工具类型 Pick 创建一个新的类型,我们从接口 Person 类型中选择了名为 "name" 和 "age" 的属性,并创建了一个新的类型 PickPerson,该类型只包含所选属性并保留了相应的属性值类型,等同于下面的这段代码:
ini
type PickPerson = {
name: string;
age: number;
}
3. 使用场景
3.1. Form 表单数据处理
在处理表单数据时,我们可以使用 Pick 从整个表单数据中选择需要提交或处理的字段。这样可以避免不必要的数据传输和处理。
typescript
interface FormData {
name: string;
age: string;
gender: string;
password: string;
confirmPassword: string;
phone: number;
email: string;
address: string;
}
type LoginFormData = Pick<FormData, "name" | "password" | "confirmPassword">;
function handleLogin(formData: LoginFormData) {
// 处理表单数据...
}
const loginData: LoginFormData = {
name: "Echo",
password: "admin123",
confirmPassword: "admin123",
};
handleLogin(loginData);
3.2. 数据库查询
当需要在数据库查询中仅返回某些字段时,可以使用 Pick 来选择要返回的字段,减少不必要的数据传输,提高查询效率。
typescript
type User = {
id: number;
name: string;
email: string;
createdTime: Date;
updatedTime: Date;
};
type UserQueryResult = Pick<User, 'id' | 'name' | 'email'>;
const queryUsers = (): UserQueryResult[] => {
// 模拟数据库查询结果
const users: User[] = [
{ id: 1, name: "Echo", email: "Echo@123test.com", createdTime: new Date(), updatedTime: new Date() },
{ id: 2, name: "Steven", email: "Steven@456test.com", createdTime: new Date(), updatedTime: new Date() },
];
return users.map((user: UserQueryResult) => ({
id: user.id,
name: user.name,
email: user.email
}));
}
const users = queryUsers();
console.log(users);
/**
* 输出
[
{ id: 1, name: 'Echo', email: 'Echo@123test.com' },
{ id: 2, name: 'Steven', email: 'Steven@456test.com' }
]
*/
还有其它的一些使用场景,大家在开发项目的过程中可以自己去摸索。
4. 注意事项
使用泛型工具类型 Pick<T, K>,需要注意以下几个问题:
4.1. 属性名称的准确性
在指定属性名称 K 时,需要确保它们确实存在于类型 T 中,否则会导致类型错误。
上面这段代码中,我们使用 Pick 来创建一个新类型,在指定属性名称时,我们选择了 name 和 id 属性,但是 id 并不是 Person 中的属性,所以代码会报错。
4.2. 属性类型的一致性
Pick 生成的新类型,具有的属性的类型与原始对象的属性类型是一致的。
也就是说,假如原始对象的属性 name 类型是 string 类型,那么使用 Pick 生成的新类型中属性 name 也是 string 类型。
ini
interface Person {
name: string;
age?: number;
gender: string;
}
type PickPerson = Pick<Person, "name" | "age">
let user: PickPerson = {
name: "Echo",
}
上面这段代码中,PickPerson 中 name 属性的类型是 string 类型,age 属性是可选属性,类型是 number 类型,与 Person 的一致。
上面的 PickPerson 就等同于下面的这段代码:
ini
type PickPerson = {
name: string;
age?: number;
}
4.3. 新类型的属性顺序可能与原始类型的属性顺序不同
Pick 选择的属性将按照它们在 K 中的顺序进行排列。
ini
interface Person {
name: string;
age: number;
gender: string;
}
type PickPerson = Pick<Person, "age" | "name">
// 等同于
/**
type PickPerson = {
age: number;
name: string;
}
*/