TS 工具类型实战:Pick/Omit/Partial/Record 一次讲透,别再 Omit 反了

为什么需要工具类型?

类型写得好的项目,维护起来像看文档;类型写得烂的项目,改一行代码要猜半小时。

一个 User 接口可能被用在列表、详情、表单、更新、权限等多个场景,每个场景需要的字段都不一样。如果每个场景都手写 interface,不仅重复,还容易漏改。TypeScript 内置的工具类型就是帮你"消费"已有类型,快速生成新类型。

keyof:一切工具类型的地基

keyof T 会取出 T 所有键组成联合类型。

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

type UserKeys = keyof User; // 'id' | 'name' | 'age' | 'email'

你会发现,keyof 是后面所有操作的"原料":没有它,你都不知道类型里有哪些键。

Pick:按需取字段

Pick 的字面意思就是"挑",从一个大类型里挑出你需要的字段,形成新类型。

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

const u: UserPreview = {
  id: 1,
  name: 'lg'
};

场景:列表项通常只需要 id + name,不需要 emailage 这些敏感或大字段;详情接口返回的 DTO 也可以用 Pick 缩小范围。说白了,Pick 就是给类型做"字段精选"。

Omit:反向剔除字段

Omit 和 Pick 相反,是"不要哪些字段"。

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

const safeUser: UserSafe = {
  id: 2,
  name: 'l',
  age: 20
};

场景:用户信息要脱敏,不发 email 给前端;更新用户时排除 idid 由路径参数决定)。

Omit 的内部实现,为什么等价于 Pick + Exclude?

TS 源码中 Omit 的实现是:

scala 复制代码
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

怎么理解?拆成三步:

  1. keyof T 拿到所有键的联合类型,例如 'id' | 'name' | 'age' | 'email'
  2. Exclude<keyof T, K> 把要剔除的 K 从联合类型中删掉,剩下需要保留的键
  3. Pick<T, 剩下的键> 再把这些键从 T 中挑出来

所以 Omit<User, 'email'> 等价于 Pick<User, Exclude<keyof User, 'email'>>,也就是 Pick<User, 'id' | 'name' | 'age'>

一句话:Omit 不是新魔法,它只是 Pick + Exclude 的组合拳。

Exclude 和 Omit 的区别

  • Exclude<联合类型, 要排除的成员>:从联合类型 中删除某些成员,返回新的联合类型
  • Omit<对象类型, 要排除的键>:从对象类型 中删除某些键,返回新的对象类型

Exclude 处理的是联合类型 ,Omit 处理的是对象接口

ini 复制代码
type All = 'id' | 'name' | 'age' | 'email';
type AfterExclude = Exclude<All, 'email'>; // 'id' | 'name' | 'age'

Exclude 的第二个参数可以是单个字符串,也可以是联合类型,表示要删除哪些。

一个极易踩的坑:Omit 第二个参数是"要删的键",不是"要保留的键"

看这段代码:

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

type UserKeys = keyof User; // 'id' | 'name' | 'age' | 'email'
type KeepKeys = Exclude<UserKeys, 'email'>; // 'id' | 'name' | 'age'

type MyOmitUser = Omit<User, KeepKeys>;

如果你以为 KeepKeys 是"保留的键",那就反了。Omit<User, KeepKeys> 会剔除 id/name/age,最后只剩下 email

ini 复制代码
type MyOmitUser = { email: string };

如果你想排除 email,应该直接写:

ini 复制代码
type UserSafe = Omit<User, 'email'>; // { id: number; name: string; age: number }

记住:Omit 是"不要",不是"只要"。要保留用 Pick,要删除用 Omit。

Partial:所有字段可选

Partial<T>T 的所有字段变成可选。

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

const patchUser: PartialUser = {
  name: 'm',
  age: 18
};

const emptyObj: PartialUser = {}; // 合法

场景:更新接口的 patch body、表单草稿、部分配置合并。对象属性很多时,Partial 能省去大量重复的 ?:

内部实现也很简单:

ini 复制代码
type Partial<T> = {
  [P in keyof T]?: T[P];
};

Record:快速构造键值对

Record<K, V> 用来构造"键类型为 K、值类型为 V"的对象。

ini 复制代码
type Dict = Record<string, number>;
const obj: Dict = { a: 1, b: 2 };

最经典的场景是 HTTP 状态码映射:

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

const errorMessage: ErrorMsgMap = {
  400: '请求参数错误',
  401: '未登录,请重新登录,没有权限',
  403: '权限不足,禁止访问',
  404: '资源不存在',
  500: '服务器内部错误'
};

function getErrMsg(code: number): string {
  return errorMessage[code] ?? '未知错误';
}

Record<number, string> 表示一个 key 是 number、value 是 string 的对象。相比 { [code: number]: string },Record 的语义更清晰,也更适合作为工具类型的输出。

ReturnType:获取函数返回类型

有时候你不想重复声明函数返回值的类型,或者第三方库没有导出返回类型,可以用 ReturnType 自动推导。

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

type FnReturn = ReturnType<typeof fn>; // { x: number; y: number }

注意要用 typeof fn 获取函数类型,而不是 fnReturnType 在 Redux action、Express handler、工具函数封装里很常见。

一张表总结

工具类型 作用 典型场景
keyof T 取所有键联合 动态属性、遍历
Pick<T, K> 挑选字段 列表项、DTO
Omit<T, K> 剔除字段 脱敏、排除 id
Exclude<U, K> 从联合类型中删除 联合类型过滤
Partial<T> 所有字段可选 更新、表单草稿
Record<K, V> 构造键值对 状态码、字典
ReturnType<F> 获取函数返回类型 第三方库、封装

写在最后

工具类型不是炫技,而是让类型像积木一样复用。你写得越多,越会发现:类型定义的成本不在"写",而在"改";工具类型能帮你把改动收敛到一处。

下次在项目里遇到"这个类型跟那个类型就差几个字段"的时候,先别急着新建 interface,想想 Pick、Omit、Partial 是不是已经够用了。

相关推荐
大模型丫丫7 小时前
用 TypeScript、NestJS 和 LangGraph 搭建一个可控的 AI Agent
javascript·人工智能·typescript
shmily麻瓜小菜鸡8 小时前
JavaScript / TypeScript 易踩坑知识点 —— 异步编程类
开发语言·javascript·typescript
妙码生花11 小时前
使用git更新ai-go-admin框架
前端·人工智能·git·golang·typescript·php
这个DBA有点耶1 天前
MySQL 8.0执行计划分析利器:EXPLAIN ANALYZE到底比EXPLAIN强在哪?
数据库·mysql·代码规范
独立开发者屿风3 天前
在 React 里把 import 排得明明白白:一份让人极度舒适的配置指南
react.js·代码规范
行者全栈架构师3 天前
WorkBuddy 开放平台个人开发者接入实战:从零到 Agent 应用的完整路径
人工智能·架构·代码规范
晴天163 天前
Tsup:TypeScript库打包的“零配置极速方案”
前端·javascript·typescript
shmily麻瓜小菜鸡3 天前
JavaScript / TypeScript 易踩坑知识点 —— 作用域与变量类
开发语言·javascript·typescript