别只会用 Pick/Omit/Partial!从拼多多一道面试题搞懂 TS 全部内置工具类型
面试官一句"手写一个 Omit",其实是想看你有没有掌握工具类型的组合思维。本文用一道真实面试题,把 TS 内置工具类型一次讲透。
前言
拼多多前端一面的题目很朴素:"手写 Omit<T, K> 的实现"。
这道题看着简单,却能精准筛选出两种人:只会背 API 的,和真正理解工具类型底层组合的。
- 前者:熟练写出
Omit<User, 'email'> - 后者:能写出
Pick<User, Exclude<keyof User, "email">>,并讲清每一步发生了什么
本文就沿着这道题,把 Pick、Omit、Partial、Record、ReturnType、Exclude、keyof 这几个 TS 内置工具类型一次讲透,每个都配真实的业务场景。
- 适合谁:准备前端面试、想系统搞懂 TS 类型编程的开发者
- 读完收获:看懂工具类型怎么组合 + 面试手写不再慌 + 业务里用对场景
- 文末附有完整可验证代码 🧪
项目概览
题目围绕一个 User 接口展开,用各种工具类型派生新类型:
ts
interface User {
id: number;
name: string;
age: number;
email: string;
}
用到的工具类型全家福:
| 工具类型 | 作用 | 本 Demo 场景 |
|---|---|---|
keyof |
拿到类型的所有键(联合类型) | 工具类型的地基 |
Pick<T, K> |
挑选部分字段 | 列表页预览 UserPreview |
Omit<T, K> |
剔除部分字段 | 安全版 UserSafe |
Partial<T> |
所有字段变可选 | 更新接口 patch 请求 |
Record<K, V> |
构造键值映射 | HTTP 错误码表 |
ReturnType<T> |
取函数返回类型 | 推导函数返回值 |
Exclude<T, U> |
从联合类型中剔除 | Omit 手写的零件 |
核心知识点
知识点 1:keyof ------ 工具类型的"地基"
keyof 不是工具类型,却是一切工具类型的基石。它的作用一句话:把类型的所有键,变成一个联合类型(union)。
ts
type UserKeys = keyof User;
// 等价于 type UserKeys = "id" | "name" | "age" | "email";
| 代码 | 作用 |
|---|---|
keyof User |
取出 User 的 4 个键名,拼成联合类型 |
| `"id" | "name" |
为什么这么重要?因为所有工具类型都要先知道类型上有哪些键 ,才能决定保留还是剔除。
keyof就像一把万能钥匙,先把类型这个"钥匙柜"打开。
知识点 2:Pick ------ 从类型里"挑"字段
Pick<T, K> 从类型 T 中,挑选出 K 指定的字段,形成新类型。像点菜------菜单上 4 个菜,你只挑 2 个上桌:
ts
type UserPreview = Pick<User, 'id' | 'name'>;
const u: UserPreview = {
id: 1,
name: 'dcx', // ✅ 只有 id 和 name
// age: 18, // ❌ 多了会报错
};
| 代码 | 作用 |
|---|---|
| `Pick<User, 'id' | 'name'>` |
const u: UserPreview |
赋值时,多一个字段、少一个字段都会类型报错 |
业务场景 :列表页往往不需要 email 这种敏感字段,用
Pick直接从原类型"裁剪"出一个预览版,字段变了原类型自动同步,不用写两遍接口类型。
知识点 3:Omit ------ 剔除字段 + 面试手写核心
Omit<T, K> 和 Pick 相反,去掉 K 指定的字段。最关键的来了------TS 官方文档明确写道:
Omit<T, K> = Pick<T, Exclude<keyof T, K>>
也就是说,Omit 的底层是用三个"零件"组合出来的。面试时手写 2.ts 的答案就是这 3 步:
ts
type UserKeys = keyof User; // ① "id"|"name"|"age"|"email"
type KeepKeys = Exclude<UserKeys, "email">; // ② 剔除 email → "id"|"name"|"age"
type MyOmitUser = Pick<User, KeepKeys>; // ③ 挑选剩下的 → 去掉 email 的 User
| 步骤 | 代码 | 发生了什么 |
|---|---|---|
| ① | keyof User |
打开钥匙柜,拿到 4 把钥匙:`"id" |
| ② | Exclude<..., "email"> |
把 "email" 这把钥匙扔掉,剩 3 把 |
| ③ | Pick<User, KeepKeys> |
拿着剩下的 3 把钥匙,从 User 里挑出 3 个字段 |
这就是面试官真正想考的东西------组合思维。 工具类型不是 7 个孤立的 API,而是可以互相拼装的"积木":
keyof开锁 →Exclude丢钥匙 →Pick捡钥匙。能把Omit拆成这三步,说明你真的理解了类型编程,而不是死记硬背。
应用场景拆解
场景 1:UserPreview ------ 列表页隐藏敏感字段
接口给整个 User,前端只想展示 id 和 name:
ts
type UserPreview = Pick<User, 'id' | 'name'>;
直接
Pick出预览版,不拷贝、不重复定义,源类型变了这里自动跟着变。
场景 2:UserSafe ------ 剔除 email
返回给前端的对象不能带 email(隐私),用 Omit:
ts
type UserSafe = Omit<User, 'email'>;
const SafeUser: UserSafe = { id: 1, name: 'dcx', age: 18 };
Omit和Pick一个"删"一个"留",是一对互补操作。
场景 3:Partial ------ 更新接口的 patch 请求
改用户资料时,可能只改一个字段(比如只改 age),全部字段都填又太麻烦。Partial<User> 让所有字段都可选:
ts
type PartialUser = Partial<User>;
const patchUser: PartialUser = { id: 1, name: 'dcx' }; // ✅ 只改需要的
const emptyObj: PartialUser = {}; // ✅ 甚至可以空
这就是 PATCH 接口(部分更新)的天然类型,也是
Partial名字的由来:部分(partial)更新。
场景 4:Record ------ 键值映射,HTTP 错误码表
Record<K, V> 构造"键的类型为 K、值的类型为 V"的对象。最适合做字典表------比如 HTTP 状态码 → 错误提示:
ts
type ErrorMsgMap = Record<number, string>;
const errorMessage: ErrorMsgMap = {
400: "请求参数错误",
401: "未登录,请重新登录",
404: "资源不存在",
500: "服务器内部错误",
};
function getErrMsg(code: number): string {
return errorMessage[code] ?? "未知错误";
}
| 代码 | 作用 |
|---|---|
Record<number, string> |
键必须是 number,值必须是 string |
errorMessage[code] |
按状态码取提示 |
?? "未知错误" |
取不到就返回兜底文案(空值合并运算符) |
有了
Record<number, string>约束,误写errorMessage: { "bad": "出错" }会立刻类型报错------键的类型不对。
补充:ReturnType 和 Exclude
ts
function fn() { return { x: 1, y: 2 }; }
type fnReturn = ReturnType<typeof fn>; // { x: number; y: number }
type All = "id" | "name" | "age" | "email";
type AfterExclude = Exclude<All, "email">; // "id" | "name" | "age"
ReturnType自动提取函数返回的类型,不用手写;Exclude是对联合类型 做剔除------注意它只处理联合类型,而Omit处理的是对象类型 ,这也是 1.ts 注释里那句"Exclude处理联合类型,Omit处理对象接口"的含义。
总结
回头看这道拼多多面试题,其实考了 4 层能力:
- ✅ 背 API :知道
Pick挑字段、Omit删字段 - ✅ 懂组合 :
Omit = Pick + Exclude + keyof,能手写出来 - ✅ 明原理 :
keyof拿联合类型、Exclude操作联合类型、Pick操作对象类型 - ✅ 会应用:知道列表用 Pick、PATCH 用 Partial、字典表用 Record
记忆口诀:keyof 数钥匙 🔑 → Exclude 丢钥匙 → Pick 捡钥匙。下次面试官再让你手写,你就按这个思路拆。
进一步可以试试手写 Partial、Readonly、Required(都用 in 操作符遍历键),理解了"积木组合",这些都能自己拼出来。你学 TS 工具类型时有没有遇到过什么记不住的场景?欢迎评论区聊聊 👏
完整项目代码
项目已托管在 Gitee:
文件树:
bash
backend/interview/ts/pick_partial/
├── readme.md # 面试题笔记 + 知识点
├── 1.ts # Pick/Omit/Partial/Record/ReturnType 应用场景
└── 2.ts # 手写 Omit 实现(面试答案)
快速验证(2 步):
bash
# 1. 克隆仓库并进入目录
git clone git@gitee.com:dcx2758/ai_doubao_dcx.git
cd ai_doubao_dcx/backend/interview/ts/pick_partial
# 2. 用 tsc 做类型检查(零报错即通过)
npx tsc --noEmit --strict 1.ts 2.ts
依赖清单:
| 依赖 | 版本要求 | 说明 |
|---|---|---|
| TypeScript | ≥ 4.1 | 内置工具类型 Omit 自 3.5 引入,4.1 后完整支持 |
| Node.js | ≥ 16 | 运行 npx 即可,无需装其他依赖 |
把 2.ts 里的 MyOmitUser 换成任意接口试手,就能验证你对工具类型组合的理解了 🎯