TypeScript 工具类型怎么记:从 Pick、Omit 到 keyof 与 Exclude 推导

TypeScript 工具类型怎么记:从 Pick、Omit 到 keyof 与 Exclude 推导

学习 TypeScript 时,很多人能背出 PickOmitPartial,遇到组合题却仍会把"保留"和"删除"写反。关键不是继续背定义,而是先判断工具类型的输入是什么、输出是什么 。本文用一个 User 类型串起常见工具类型,并重点推导 keyof → Exclude → Pick 如何构成 Omit

本文根据现有 TypeScript 学习代码进行静态分析,代码编译运行未验证。

一张表区分常见工具类型

先记住它们各自处理什么:

工具 输入 输出 核心作用
keyof T 对象类型 属性名联合类型 取出全部键
Pick<T, K> 对象类型、键 对象类型 保留指定属性
Omit<T, K> 对象类型、键 对象类型 删除指定属性
Exclude<T, U> 联合类型、待排除类型 联合类型 排除联合成员
Partial<T> 对象类型 对象类型 全部属性变为可选
Required<T> 对象类型 对象类型 全部属性变为必填
Record<K, V> 键类型、值类型 对象映射类型 约束键和值
ReturnType<F> 函数类型 返回值类型 提取函数返回类型

最容易混淆的是:Exclude 处理联合类型,Omit 处理对象属性。

用 User 理解 Pick 与 Omit

基础类型如下:

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

Pick:保留指定属性

typescript 复制代码
type UserPreview = Pick<User, 'id' | 'name'>;

const user: UserPreview = {
  id: 1,
  name: '张三',
};

Pick<User, 'id' | 'name'> 可以读成:从 User 中挑出 idname。其中第二个参数必须是 User 已有的键。

Omit:删除指定属性

typescript 复制代码
type UserSafe = Omit<User, 'email'>;

const userSafe: UserSafe = {
  id: 1,
  name: '张三',
  age: 18,
};

Omit<User, 'email'> 删除 email,保留其余属性。记忆时只抓住方向:

  • Pick 的第二个参数是"要保留的键";
  • Omit 的第二个参数是"要删除的键"。

keyof 与 Exclude 分别做什么

keyof:对象键变成联合类型

typescript 复制代码
type UserKeys = keyof User;
// 'id' | 'name' | 'age' | 'email'

这里的输出已经不是对象,而是属性名组成的联合类型。

Exclude:从联合类型排除成员

typescript 复制代码
type KeepKeys = Exclude<UserKeys, 'email'>;
// 'id' | 'name' | 'age'

Exclude<T, U> 会从联合类型 T 中排除可赋给 U 的成员。因此,它适合处理键的联合类型,但不能直接代替 Omit 处理对象。

Omit 为什么能由 keyof、Exclude、Pick 组成

核心思想可以写成:

typescript 复制代码
type MyOmit<T, K extends keyof T> =
  Pick<T, Exclude<keyof T, K>>;

以删除 email 为例,类型数据流分三步:

text 复制代码
User
  ↓ keyof
'id' | 'name' | 'age' | 'email'
  ↓ Exclude<..., 'email'>
'id' | 'name' | 'age'
  ↓ Pick<User, ...>
{ id: number; name: string; age: number }

对应代码:

typescript 复制代码
type AllKeys = keyof User;
type RemainingKeys = Exclude<AllKeys, 'email'>;
type UserWithoutEmail = Pick<User, RemainingKeys>;

因此,理解 Omit 的重点不是背一行实现,而是理解三个阶段:

  1. keyof 把对象转换为键的联合类型;
  2. Exclude 从联合类型中删除不要的键;
  3. Pick 根据剩余键重新构造对象类型。

高频易错题:KeepKeys 传给 Omit 会怎样

下面这段代码非常容易判断错误:

typescript 复制代码
type UserKeys = keyof User;
type KeepKeys = Exclude<UserKeys, 'email'>;
type UserOmit = Omit<User, KeepKeys>;

KeepKeys 的值是:

typescript 复制代码
'id' | 'name' | 'age'

但把它传给 Omit 后,这些键会被删除,所以实际结果是:

typescript 复制代码
type UserOmit = {
  email: string;
};

错误根因是变量名表达"要保留的键",但 Omit 的第二个参数表达"要删除的键"。

如果要保留 KeepKeys,应该写:

typescript 复制代码
type UserPicked = Pick<User, KeepKeys>;

如果只是排除 email,应该直接写:

typescript 复制代码
type UserWithoutEmail = Omit<User, 'email'>;

Partial 与 Required:控制属性是否必填

Partial:全部属性变为可选

typescript 复制代码
type PartialUser = Partial<User>;

const patch: PartialUser = {
  name: '李四',
};

它适合描述"只更新部分字段"的参数。Partial<User> 不代表只保留部分字段,而是让每个字段都可以不传。

Required:全部属性变为必填

typescript 复制代码
interface UserForm {
  name?: string;
  email?: string;
}

type CompleteUserForm = Required<UserForm>;

Required<T> 主要移除属性上的可选修饰符。需要注意:它不等于无条件删除值类型中显式声明的 undefined

Record:描述键值映射

Record<K, V> 用来创建"键为 K、值为 V"的对象类型:

typescript 复制代码
type ErrorMessages = Record<number, string>;

const errorMessages: ErrorMessages = {
  400: '请求参数错误',
  401: '未登录,请先登录',
  403: '没有权限访问',
  404: '资源找不到',
  500: '服务器错误',
};

结合空值合并运算符 ??,可以提供默认消息:

typescript 复制代码
function getErrorMessage(code: number): string {
  return errorMessages[code] ?? '未知错误';
}

?? 只在左侧为 nullundefined 时返回右侧值。

状态码分类中,1xx 更准确地说是信息响应,2xx 才是成功响应。

ReturnType:从函数提取返回类型

typescript 复制代码
function getPoint() {
  return { x: 1, y: 2 };
}

type Point = ReturnType<typeof getPoint>;
// { x: number; y: number }

这里有两步:

  1. typeof getPoint 在类型上下文中取得函数类型;
  2. ReturnType 从函数类型中提取返回值类型。

面试回答模板

面试官问:"Omit 是怎么实现的?"可以这样回答:

Omit<T, K> 用于从对象类型 T 中删除键 K。它的核心思路是先通过 keyof T 得到全部属性名,再用 Exclude 排除 K,最后用 Pick 根据剩余键构造新对象类型,因此可以理解为 Pick<T, Exclude<keyof T, K>>。其中 Exclude 操作联合类型,PickOmit 操作对象属性。

如果继续追问 PickOmit 的区别:

Pick 的第二个参数表示保留哪些键,Omit 的第二个参数表示删除哪些键。判断组合题时应先算出键的联合类型,再确认这些键传入的是 Pick 还是 Omit

最终自检清单

遇到工具类型题时,按下面顺序检查:

  • 当前输入是对象类型、联合类型,还是函数类型?
  • keyof 得到的是对象还是键的联合类型?
  • Pick 的键是要保留,还是要删除?
  • Omit 的键是要保留,还是要删除?
  • Exclude 是否正在处理联合类型?
  • ReturnType 接收的是不是函数类型?
  • 类型变化只发生在类型检查阶段,还是会生成运行时代码?

TypeScript 工具类型只参与类型检查,不会生成对应的运行时代码。真正建立体系的方法,是沿着"输入类型 → 中间类型 → 输出类型"逐步推导,而不是孤立背诵名称。

相关推荐
苏灿烤鱼1 小时前
十个 CLI 坐进一间办公室,协调层靠得住吗?
typescript·github·agent
BreezeJiang1 小时前
别再死记 TypeScript 工具类型:看懂 Omit 的类型数据流
typescript
触底反弹1 小时前
🔥 NestJS 从零到实战:一个 Todos CRUD 搞懂企业级后端框架的核心设计
后端·typescript·nestjs
深念Y1 小时前
Wine 运行 HiTool 踩坑记录
linux·windows·容器·桌面·wine·虚拟器
bksczm1 小时前
Linux之网络层协议(IP协议)
linux·网络·tcp/ip
深念Y1 小时前
无头电视盒子改服务器调优记录
linux·运维·服务器·串口·嵌入式·电视盒子·海思
我是小灰灰吖2 小时前
Qt 6.9.3 Ubuntu 22.04 虚拟键盘显示问题全记录与解决方案
linux·qt·ubuntu
Mortalbreeze2 小时前
深入理解TCP协议(一):TCP报文格式详解
linux·服务器·网络·tcp/ip
三言老师2 小时前
K8s 集群 LocalPV 静态 PV 资源手动创建实操
linux·运维·服务器·kubernetes