普通视图

发现新文章,点击刷新页面。
今天 — 2026年4月15日首页

面试官视角:TypeScript Pick 工具类型深度解析与手写实现

作者 Yira
2026年4月15日 00:58

在字节、阿里等大厂的 TypeScript 面试中,考察工具类型(Utility Types)是一个非常经典的环节。面试官并不只是想看你背诵 Pick 或 Omit 的用法,而是想通过“手写 MyPick”这道题,考察你对泛型(Generics)、索引类型查询(keyof)、映射类型(Mapped Types)以及类型约束(extends)的深度理解。

这篇文章将带你从“知其然”到“知其所以然”,用幽默且硬核的方式彻底拿下这个知识点。


为什么我们需要 Pick?(面试官的潜台词)

在写代码时,我们经常会遇到这种情况:后端定义了一个巨大的 User 对象,包含 idnameagepasswordcreatedAt 等十几个字段。但在前端的一个小卡片组件里,我只需要展示 name 和 avatar

如果不使用 Pick,你可能需要重新定义一个接口,或者手动去 extends 然后重写属性。这不仅啰嗦,而且一旦后端改了字段,你的代码维护起来就是灾难。

Pick 的本质:它就像是一个“类型级的过滤器”。你给它一个完整的对象类型,再给它几个你想要的字段名,它就能给你吐出一个全新的、精简的类型。


庖丁解牛:手写 MyPick 的三步走战略

面试官让你在 type MyPick<T, K> = any 的 any 处填空,你该如何思考?我们可以把这个过程拆解为三个步骤:

第一步:明确原材料(泛型参数)

我们需要两个参数:

  • T:原始的、完整的对象类型(比如 User)。
  • K:我们想要挑选出来的属性名(比如 'name' | 'age')。

第二步:加上安全锁(类型约束)

这是面试中最容易丢分的地方。如果用户传了一个 T 中不存在的属性怎么办?比如 Pick<User, 'nonExistentField'>
为了防止这种情况,我们必须限制 KK 必须是 T 中所有键的集合的子集。

这就引入了 keyof T 和 extends

  • keyof T:获取 T 所有属性名组成的联合类型(例如 'id' | 'name' | 'age')。
  • K extends keyof T:这句话的意思是,“K 必须是 keyof T 的一部分”。如果传了不存在的属性,TypeScript 会直接报错,这就是类型安全。

第三步:加工生产(映射类型)

拿到了合法的 K,我们需要构建新对象。这里要用到映射类型
语法结构是:{ [P in K]: ... }
这就像是一个 for...in 循环,遍历 K 中的每一个属性 P,然后去原始类型 T 中查找 P 对应的类型(即 T[P],这叫索引访问类型)。


核心代码实现与逐行精讲

结合上述思路,我们可以写出以下完美的实现代码:

1// 1. 定义原始类型
2interface User {
3    id: number;
4    age: number;
5    name: string;
6    password: string; // 敏感字段
7}
8
9// 2. 手写 MyPick
10// T: 源类型
11// K: 需要挑选的键,且 K 必须受限于 keyof T (即 K 必须是 T 中存在的属性)
12type MyPick<T, K extends keyof T> = {
13    // 映射类型:遍历 K 中的每一个属性 P
14    [P in K]: T[P]; // T[P] 表示取出 T 中 P 属性对应的类型
15}
16
17// 3. 测试
18type UserName = MyPick<User, 'name'>; 
19// 结果:{ name: string }
20
21type UserPublicInfo = MyPick<User, 'id' | 'name'>;
22// 结果:{ id: number; name: string; }
23
24// 4. 错误测试 (TypeScript 会报错,因为 'hack' 不在 User 中)
25// type ErrorCase = MyPick<User, 'hack'>; 

关键知识点深度解析

为了在面试中对答如流,你需要理解以下几个核心概念:

keyof 操作符
它的作用是“取键”。对于一个对象类型,keyof 会返回它所有属性名的联合类型。

  • 例子:keyof User 得到 'id' | 'age' | 'name' | 'password'

索引访问类型
语法是 T[P]。它的作用是“取值”。

  • 例子:如果 P 是 'name',那么 User['name'] 就是 string

映射类型
语法是 { [P in K]: ... }。它允许你将一个联合类型转换为一个新的对象类型。

  • 在 MyPick 中,我们遍历的是 K(用户想要的键),而不是 keyof T(所有的键),这就是“挑选”的精髓。

extends 关键字
在这里它不是“继承”,而是“约束”。K extends keyof T 保证了传入的键是合法的。


举一反三:Omit 与 Partial

面试官通常会接着问:“那你能手写一下 Omit 吗?”
其实 Omit 就是 Pick 的反面。Omit 是“排除”某些字段。
它的实现思路是:先利用 Exclude 工具类型从 keyof T 中剔除掉 K,剩下的就是我们要保留的,然后再用 Pick 的逻辑。

1// 手写 Omit
2// Exclude<UnionType, ExcludedMembers> 用于从联合类型中排除某项
3type MyOmit<T, K extends keyof T> = MyPick<T, Exclude<keyof T, K>>;

Partial
Partial 则是将所有属性变为可选。

1type MyPartial<T> = {
2    [P in keyof T]?: T[P];
3}

总结

在面试中回答这道题,建议遵循以下逻辑流:

  1. 定义泛型:声明 T 和 K
  2. 添加约束:使用 K extends keyof T 确保类型安全。
  3. 构建映射:使用 { [P in K]: T[P] } 完成类型的重组。

掌握了这个模板,你不仅搞定了 Pick,也顺手拿下了 OmitReadonly 和 Partial,它们是 TypeScript 高级类型编程的基石。

❌
❌