TypeScript 工具类型一篇讲透:Pick、Omit、Partial、Exclude、Record、ReturnType、keyof
本文是一篇 TypeScript 学习日志。高级类型(工具类型)是 TS 进阶的必经之路,也是面试高频考点。本文用一个
User类型贯穿始终,把最常用的 7 个工具类型 ------keyof、Pick、Omit、Partial、Exclude、Record、ReturnType逐个讲清,并手写它们的源码实现,让你彻底搞懂Omit为什么等价于Pick<T, Exclude<keyof T, K>>。
目录
- 一、为什么要学工具类型
- 二、keyof:获取所有属性名
- 三、Pick:从类型里挑字段
- 四、Omit:从类型里删字段
- 五、Partial:所有字段变可选
- 六、Exclude:从联合类型里排除
- [七、Record:构造 key-value 映射](#七、Record:构造 key-value 映射 "#%E4%B8%83record%E6%9E%84%E9%80%A0-key-value-%E6%98%A0%E5%B0%84")
- 八、ReturnType:获取函数返回值类型
- 九、综合实战
- 十、总结
一、为什么要学工具类型
先看一个最常出现的需求:「一个类型里,挑选一些需要的字段,形成一个新的类型」。
假设我们定义了一个完整的 User 类型:
typescript
interface User {
id: number;
name: string;
age: number;
sex: string;
email: string;
}
在实际项目里,我们几乎不会一直用「全量」的 User,而是会遇到各种「变形」需求:
- 展示用户列表,只要
id和name(不要 email 这种敏感字段); - 返回给第三方,排除
email; - 修改用户信息,字段全部可选(PATCH 部分更新);
- 定义 HTTP 状态码 → 错误文案的映射表。
这些需求如果每个都手动重新定义类型,字段一多就会大量重复、难以维护。工具类型 就是 TypeScript 官方和内置的「类型变形函数」,帮你从已有类型派生出新类型。
一句话理解:工具类型 = 操作类型的函数,输入一个类型,输出一个新的类型。
下面用同一个 User 类型,把最常用的几个工具类型逐个吃透。
二、keyof:获取所有属性名
keyof T 的作用:从一个类型 T 中,取出所有的属性名,组成一个联合类型。
typescript
interface User {
id: number;
name: string;
age: number;
sex: string;
email: string;
}
type UserKeys = keyof User;
// type UserKeys = 'id' | 'name' | 'age' | 'sex' | 'email'
keyof User得到的是'id' | 'name' | 'age' | 'sex' | 'email';- 注意:这是字符串字面量类型的联合 ,不是普通的
string。
keyof 是所有「对象类型工具」的地基,后面的 Pick、Omit 都建立在它之上。
三、Pick:从类型里挑字段
3.1 用法
Pick<T, K> 的作用:从类型 T 中,选取 K 指定的属性,形成一个新的类型。
typescript
type UserPrivate = Pick<User, 'id' | 'name'>;
const u: UserPrivate = {
id: 1,
name: 'zj',
};
// ✅ 只允许 id 和 name,多写或少写都会报错
UserPrivate 等价于手写:
typescript
type UserPrivate = {
id: number;
name: string;
};
3.2 源码实现
Pick 官方源码只有一行:
typescript
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
拆解:
K extends keyof T:约束 K 必须是 T 的某个属性名(否则编译报错);[P in K]:映射类型,遍历联合类型 K 里的每个属性名 P;T[P]:索引访问类型,取出 T 里 P 对应的值类型。
典型场景 :对外接口只暴露部分字段、做权限控制(UserPrivate 不带 email 敏感信息)。
四、Omit:从类型里删字段
4.1 用法
Omit<T, K> 的作用:从类型 T 中,排除 K 指定的属性,形成一个新的类型 (和 Pick 正好相反)。
typescript
type UserSafe = Omit<User, 'email'>;
const uSafe: UserSafe = {
id: 2,
name: 'zj',
age: 18,
sex: '男',
};
// ✅ 除 email 外都要有
UserSafe 等价于:
typescript
type UserSafe = {
id: number;
name: string;
age: number;
sex: string;
};
4.2 源码实现:Omit 为什么 = Pick + Exclude + keyof
这是本文最核心的一个知识点。Omit 官方源码:
typescript
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
你可能会在面试里被问到:「Omit<T, K> 等价于 Pick<T, Exclude<keyof T, K>>,怎么理解?」
我们一步步拆:
第一步 keyof T:取出 T 的所有属性名。
typescript
keyof User
// → 'id' | 'name' | 'age' | 'sex' | 'email'
第二步 Exclude<keyof T, K>:从「所有属性名」里排除掉 K。
typescript
Exclude<'id' | 'name' | 'age' | 'sex' | 'email', 'email'>
// → 'id' | 'name' | 'age' | 'sex' (去掉了 email)
第三步 Pick<T, ...>:用剩下的属性名,从 T 里挑出对应的字段。
typescript
Pick<User, 'id' | 'name' | 'age' | 'sex'>
// → { id: number; name: string; age: number; sex: string }
所以 Omit 的完整逻辑就是:「先算要保留哪些字段(keyof 全量 - 排除项),再用 Pick 挑出来」。
💡 一句话记牢:
Omit= 先Exclude求出要保留的 key,再Pick出来。
五、Partial:所有字段变可选
5.1 用法
Partial<T> 的作用:把类型 T 的所有字段都变成可选(?)。
typescript
type PartialUser = Partial<User>;
const uPartial: PartialUser = {
id: 3,
name: 'zj',
sex: '男',
};
// ✅ 字段可写可不写,写几个都行
const emptyObj: PartialUser = {}; // ✅ 甚至可以为空对象
5.2 源码实现
typescript
type Partial<T> = {
[P in keyof T]?: T[P];
};
拆解:
[P in keyof T]:遍历 T 的所有属性名;?:给每个属性加「可选」修饰符;T[P]:保留原来的值类型。
典型场景 :PATCH 接口的部分更新。比如 NestJS 里的 update(id, patch: Partial<Todo>),前端只传要改的字段即可,不用传全量。
六、Exclude:从联合类型里排除
6.1 用法
Exclude<T, U> 的作用:从联合类型 T 中,排除掉 U 里出现的类型。
typescript
type All = 'id' | 'name' | 'age' | 'sex' | 'email';
type AfterExclude = Exclude<All, 'email'>;
// type AfterExclude = 'id' | 'name' | 'age' | 'sex'
6.2 源码实现
typescript
type Exclude<T, U> = T extends U ? never : T;
这里用到了**「分布式条件类型」**:
- 当 T 是联合类型时,
T extends U ? never : T会把联合类型的每个成员单独判断; - 如果某个成员属于 U,就变成
never(从结果里去掉); - 否则保留该成员。
💡 关键区分:
Exclude处理的是「联合类型」 (比如keyof User的结果),Omit处理的是「对象类型」 (比如User)。别搞混。
七、Record:构造 key-value 映射
7.1 用法
Record<K, V> 的作用:构造一个对象类型,key 的类型是 K,value 的类型是 V。
typescript
type Dict = Record<string, number>;
const obj: Dict = {
a: 1,
b: 2,
};
// ✅ 键是 string,值是 number
最经典的实战场景:HTTP 状态码 → 错误文案的映射表。
typescript
// key 是 number(状态码),value 是 string(错误文案)
type ErrorMsgMap = Record<number, string>;
const errorMsg: ErrorMsgMap = {
400: '请求参数错误',
401: '未登录',
402: '未支付',
403: '权限不足',
404: '资源不存在',
500: '服务器错误',
};
function getErrorMsg(code: number) {
return errorMsg[code] ?? '未知错误';
}
getErrorMsg(404); // '资源不存在'
getErrorMsg(999); // '未知错误'
7.2 HTTP 状态码科普(顺手记住)
| 范围 | 含义 |
|---|---|
1xx |
执行中 / 信息响应 |
2xx |
成功 |
3xx |
跳转 / 重定向 |
4xx |
客户端错误 |
5xx |
服务器错误 |
7.3 源码实现
typescript
type Record<K extends keyof any, T> = {
[P in K]: T;
};
K extends keyof any:约束 K 必须是合法的键类型(string / number / symbol);[P in K]: T:遍历 K 的每个键,值类型统一为 T。
八、ReturnType:获取函数返回值类型
8.1 用法
ReturnType<T> 的作用:获取一个函数类型 T 的返回值类型。
typescript
function fn() {
return { x: 1, y: 2 };
}
type FnReturn = ReturnType<typeof fn>;
// type FnReturn = { x: number; y: number }
typeof fn:拿到函数fn的类型(不是调用它);ReturnType<...>:提取这个函数类型的返回值类型。
8.2 源码实现
typescript
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
这里用到了 infer 推断:
infer R:让 TS 自动推断返回值类型,并用 R 占位;- 如果 T 是函数类型,就返回推断出的 R;否则返回
any。
典型场景 :复用某个函数的返回值类型,避免手写重复;或封装 Redux 里的 RootState = ReturnType<typeof store.getState>。
九、综合实战
把上面的工具类型串起来,看一个贴近真实项目的例子:
typescript
interface User {
id: number;
name: string;
age: number;
sex: string;
email: string;
}
// 1. 列表展示:只要 id 和 name
type UserListItem = Pick<User, 'id' | 'name'>;
// 2. 对外安全返回:排除 email
type PublicUser = Omit<User, 'email'>;
// 3. 部分更新:所有字段可选
type UpdateUser = Partial<User>;
// 4. 手写 MyOmit(理解 Omit 的底层)
type UserKeys = keyof User; // 所有属性名
type KeepKeys = Exclude<UserKeys, 'email'>; // 排除 email
type MyOmitUser = Pick<User, KeepKeys>; // 用剩余 key 挑字段
// MyOmitUser 和 Omit<User, 'email'> 完全等价
// 5. 状态码映射
type StatusMap = Record<number, string>;
// 6. 提取函数返回值
function createUser() {
return { id: 1, name: 'zj', email: 'z@j.com' };
}
type CreatedUser = ReturnType<typeof createUser>;
十、总结
这篇文章用同一个 User 类型,把最常用的工具类型串了一遍:
| 工具类型 | 作用 | 一句话 |
|---|---|---|
keyof T |
取所有属性名 | 得到 `'a' |
Pick<T, K> |
挑字段 | 从 T 选 K 属性 |
Omit<T, K> |
删字段 | 从 T 删 K 属性 |
Partial<T> |
全部可选 | 字段都加 ? |
Exclude<T, U> |
排除联合成员 | 从联合类型里去掉 U |
Record<K, V> |
造映射 | key 是 K,value 是 V |
ReturnType<T> |
取函数返回类型 | 用 infer 推断 |
三个必背的面试点:
Omit<T, K> = Pick<T, Exclude<keyof T, K>>------先排除求出保留 key,再 Pick;Exclude处理联合类型,Omit处理对象类型;Partial的核心用途是 PATCH 部分更新 ,ReturnType靠infer实现。
一句话总结 :工具类型就是「操作类型的函数」,掌握了
keyof(取键)、映射类型[P in K](遍历)、条件类型 +infer(推断)这三个地基,Pick/Omit/Partial/Record/ReturnType全都信手拈来。