likes
comments
collection
share

一文读懂TypeScript泛型工具类型-Pick<T, K>

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

1. 定义

在 TypeScript 中,泛型工具类型 Pick 主要用于从 Type 中选择一组属性来构造成一个新的类型。

换句话说,就是从一个复合的类型中,取出几个想要的类型,创建出一个新的类型。

Pick 工具类型有2个类型参数 T 和 K,其中:

  • T 表示的是:选择哪个对象。
  • K 表示的是:选择对象中已有的哪些属性,可以选择一个也可以选择多个,选择多个属性时采用“联合类型”的写法。

2. 源码

泛型工具类型 Pick 在 TypeScript 中的源码实现:

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

实现原理:

  • type Pick<T, K extends keyof T>: 使用关键字 type 定义一个类型别名 Pick,它接收两个泛型参数 TK。其中,第二个参数 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> 的基本用法:

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,该类型只包含所选属性并保留了相应的属性值类型,等同于下面的这段代码:

type PickPerson = {
  name: string;
  age: number;
}

3. 使用场景

3.1. Form 表单数据处理

在处理表单数据时,我们可以使用 Pick 从整个表单数据中选择需要提交或处理的字段。这样可以避免不必要的数据传输和处理。

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 来选择要返回的字段,减少不必要的数据传输,提高查询效率。

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 中,否则会导致类型错误。

一文读懂TypeScript泛型工具类型-Pick<T, K>

上面这段代码中,我们使用 Pick 来创建一个新类型,在指定属性名称时,我们选择了 name 和 id 属性,但是 id 并不是 Person 中的属性,所以代码会报错。

4.2. 属性类型的一致性

Pick 生成的新类型,具有的属性的类型与原始对象的属性类型是一致的。

也就是说,假如原始对象的属性 name 类型是 string 类型,那么使用 Pick 生成的新类型中属性 name 也是 string 类型。

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 就等同于下面的这段代码:

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

4.3. 新类型的属性顺序可能与原始类型的属性顺序不同

Pick 选择的属性将按照它们在 K 中的顺序进行排列。

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

type PickPerson = Pick<Person, "age" | "name">

// 等同于
/**
  type PickPerson = {
    age: number;
    name: string;
  }
*/