别只会用 Pick/Omit/Partial!从拼多多一道面试题搞懂 TS 全部内置工具类型

别只会用 Pick/Omit/Partial!从拼多多一道面试题搞懂 TS 全部内置工具类型

面试官一句"手写一个 Omit",其实是想看你有没有掌握工具类型的组合思维。本文用一道真实面试题,把 TS 内置工具类型一次讲透。

前言

拼多多前端一面的题目很朴素:"手写 Omit<T, K> 的实现"

这道题看着简单,却能精准筛选出两种人:只会背 API 的,和真正理解工具类型底层组合的。

  • 前者:熟练写出 Omit<User, 'email'>
  • 后者:能写出 Pick<User, Exclude<keyof User, "email">>,并讲清每一步发生了什么

本文就沿着这道题,把 PickOmitPartialRecordReturnTypeExcludekeyof 这几个 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 };

OmitPick 一个"删"一个"留",是一对互补操作。

场景 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 层能力:

  1. 背 API :知道 Pick 挑字段、Omit 删字段
  2. 懂组合Omit = Pick + Exclude + keyof,能手写出来
  3. 明原理keyof 拿联合类型、Exclude 操作联合类型、Pick 操作对象类型
  4. 会应用:知道列表用 Pick、PATCH 用 Partial、字典表用 Record

记忆口诀:keyof 数钥匙 🔑 → Exclude 丢钥匙 → Pick 捡钥匙。下次面试官再让你手写,你就按这个思路拆。

进一步可以试试手写 PartialReadonlyRequired(都用 in 操作符遍历键),理解了"积木组合",这些都能自己拼出来。你学 TS 工具类型时有没有遇到过什么记不住的场景?欢迎评论区聊聊 👏

完整项目代码

项目已托管在 Gitee:

🔗 gitee.com/dcx2758/ai_...

文件树:

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 换成任意接口试手,就能验证你对工具类型组合的理解了 🎯

相关推荐
PC2005_cloud1 小时前
DSH 沙箱机制拆解:受限令牌为何会掐断 HTTPS 请求
前端·程序员
喜欢睡觉1 小时前
TypeScript 工具类型:像拼积木一样玩转类型
前端
mmsx1 小时前
一个黑边 Bug 修了两版:自己算矩阵直接黑屏,借库重建只用了一行 setZoom
android·前端
丑过三八线1 小时前
001 -【FastAPI 入门教程】 FastAPI Helloword
前端·chrome·fastapi
禁止摆烂_才浅1 小时前
Axios 高频面试题
前端·面试·axios
何时梦醒1 小时前
TypeScript 工具类型一篇讲透:Pick、Omit、Partial、Exclude、Record、ReturnType、keyof
前端·面试·typescript
用户2181697049301 小时前
Flutter (十九) Tabbar
前端
默_笙1 小时前
🍳 受控组件和非受控组件,我纠结了一整天,最后用"房东和租客"讲明白了
前端·javascript
缓冲中请稍后1 小时前
前端HTTP请求完全指南:从基础到LLM接口调用实战
前端·面试