深入学习 TS 高级类型 Pick,让你也能在 TS 类型中实现排除法 🙈🙈🙈

Pick 是 TypeScript 中的一个实用类型,用于从已有的类型中选择一组属性,生成一个新的类型。它可以帮助我们在代码中更精细地控制类型,从而提高代码的类型安全性和可维护性。

基本使用

假设我们有一个 User 类型:

ts 复制代码
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

我们想创建一个新的类型,只包含 name 和 email 属性。我们可以使用 Pick 来实现:

ts 复制代码
type UserContactInfo = Pick<User, "name" | "email">;

const contactInfo: UserContactInfo = {
  name: "Moment",
  email: "moment@qq.com",
};

实际的应用

Pick 在实际开发中非常有用,尤其是在处理大型接口或模型时。常见的应用场景包括:

  1. 选择性暴露接口,在 API 返回值或前端组件中,只暴露必要的属性:
ts 复制代码
type PublicUserInfo = Pick<User, "name" | "email">;
  1. 简化类型定义,在处理复杂的类型或接口时,可以简化类型定义,避免重复代码:
ts 复制代码
interface CreateUserRequest extends Pick<User, "name" | "email" | "age"> {}
  1. 类型约束,在函数参数中,只允许特定的属性:
ts 复制代码
function sendEmail(user: Pick<User, "email">) {}

实现原理

Pick 类型的实现原理如下所示:

ts 复制代码
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

在上面的代码中:

  1. T 是源类型。

  2. K 是一个属性名的联合类型,表示要从 T 中选取的属性。

  3. P in K 表示循环 K 中的每个属性 P,并将其添加到新的类型中。

  4. T[P] 表示 T 类型中属性 P 的类型。

进阶使用

结合泛型函数使用

我们可以定义一个泛型函数,使用 Pick 来选择参数类型的部分属性:

ts 复制代码
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  address: string;
}

function getUserInfo<T extends keyof User>(
  user: User,
  keys: T[]
): Pick<User, T> {
  const result = {} as Pick<User, T>;
  keys.forEach((key) => {
    result[key] = user[key];
  });
  return result;
}

const user: User = {
  id: 1,
  name: "Moment",
  email: "moment@qq.com",
  age: 18,
  address: "西安",
};

const userInfo = getUserInfo(user, ["name", "email"]);
console.log(userInfo);

最终输出结果如下所示:

动态选择属性

通过结合条件类型、映射类型、索引类型查询、索引访问类型和工具类型,实现查找出接口中的可选类型:

ts 复制代码
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  address?: string;
}

// 判断属性是否为可选
type IsOptional<T, K extends keyof T> = {} extends Pick<T, K> ? K : never;

// 提取可选属性的键,并排除掉 undefined 类型
type OptionalKeys<T> = Exclude<
  {
    [K in keyof T]: IsOptional<T, K>;
  }[keyof T],
  undefined
>;

// 使用 Pick 提取可选属性的类型
type UserOptionalProperties = Pick<User, OptionalKeys<User>>;

const userOptionalProps: UserOptionalProperties = {
  address: "广州",
};

console.log(userOptionalProps);

在上面的代码中,条件类型允许我们根据类型的特性来选择不同的类型。条件类型的基本形式是 T extends U ? X : Y,意思是如果类型 T 能够赋值给类型 U,则结果是类型 X,否则是类型 Y。

ts 复制代码
type IsOptional<T, K extends keyof T> = {} extends Pick<T, K> ? K : never;

这里 {} extends Pick<T, K> 检查 {} 是否能赋值给 Pick<T, K>,即属性 K 是否可选。如果可选,结果为 K,否则为 never。

深层属性选择

使用递归类型,可以实现从嵌套对象中选择属性。

ts 复制代码
interface Address {
  street: string;
  city: string;
}

interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  address: Address;
}

// 递归地选择深层属性
type DeepPick<T, K extends keyof T> = {
  [P in K]: T[P] extends object ? DeepPick<T[P], keyof T[P]> : T[P];
};

type UserAddressOnly = DeepPick<User, "address">;

const example: UserAddressOnly = {
  address: {
    street: "中山大道",
    city: "广州",
  },
};

条件类型与 Pick 结合使用

我们可以使用条件类型来选择符合特定条件的属性:

ts 复制代码
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  isActive: boolean;
}

// 选择布尔类型的属性
type BooleanProperties<T> = {
  [K in keyof T]: T[K] extends boolean ? K : never;
}[keyof T];

type UserBooleanProperties = Pick<User, BooleanProperties<User>>;

const boo: UserBooleanProperties = {
  isActive: false,
};

最终类型如下图所示:

结合 Exclude 使用

我们可以结合 Pick 和 Exclude 来实现复杂的类型选择:

ts 复制代码
interface User {
  id: number;
  name: string;
  email: string;
  age: number;
  address: string;
  isActive: boolean;
}

// 排除特定类型的属性
type ExcludeProperties<T, U> = {
  [K in keyof T]: T[K] extends U ? never : K;
}[keyof T];

type NonStringProperties<T> = Pick<T, ExcludeProperties<T, string>>;

type UserNonStringProperties = NonStringProperties<User>;

const user: UserNonStringProperties = {
  age: 18,
  id: 7,
  isActive: true,
};

最终类型如下图所示:

总结

Pick 是 TypeScript 中一个强大的实用类型,允许你从现有类型中选择特定的属性,创建新的类型。

通过这些高级用法示例,我们可以看到 Pick 类型在 TypeScript 中的强大和灵活性。合理使用这些技术可以帮助你编写更健壮和可维护的类型安全代码。

相关推荐
雨季66614 分钟前
Flutter 三端应用实战:OpenHarmony 简易“动态内边距调节器”交互模式深度解析
javascript·flutter·ui·交互·dart
天人合一peng29 分钟前
Unity中button 和toggle监听事件函数有无参数
前端·unity·游戏引擎
会飞的战斗鸡1 小时前
JS中的链表(含leetcode例题)
javascript·leetcode·链表
方也_arkling1 小时前
别名路径联想提示。@/统一文件路径的配置
前端·javascript
毕设源码-朱学姐1 小时前
【开题答辩全过程】以 基于web教师继续教育系统的设计与实现为例,包含答辩的问题和答案
前端
qq_177767371 小时前
React Native鸿蒙跨平台剧集管理应用实现,包含主应用组件、剧集列表、分类筛选、搜索排序等功能模块
javascript·react native·react.js·交互·harmonyos
qq_177767372 小时前
React Native鸿蒙跨平台自定义复选框组件,通过样式数组实现选中/未选中状态的样式切换,使用链式调用替代样式数组,实现状态驱动的样式变化
javascript·react native·react.js·架构·ecmascript·harmonyos·媒体
web打印社区2 小时前
web-print-pdf:突破浏览器限制,实现专业级Web静默打印
前端·javascript·vue.js·electron·html
RFCEO2 小时前
前端编程 课程十三、:CSS核心基础1:CSS选择器
前端·css·css基础选择器详细教程·css类选择器使用方法·css类选择器命名规范·css后代选择器·精准选中嵌套元素
烬头88212 小时前
React Native鸿蒙跨平台采用了函数式组件的形式,通过 props 接收分类数据,使用 TouchableOpacity实现了点击交互效果
javascript·react native·react.js·ecmascript·交互·harmonyos