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,不需要 email、age 这些敏感或大字段;详情接口返回的 DTO 也可以用 Pick 缩小范围。说白了,Pick 就是给类型做"字段精选"。

Omit:反向剔除字段

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

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

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

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

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 获取函数类型,而不是 fn。ReturnType 在 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 是不是已经够用了。

相关推荐
seowmt5 小时前
结构化数据和自然语言描述哪个更管用?答案分场景
人工智能·google·typescript·go语言·schema
进击的明明7 小时前
TypeScript速通笔记(上)
前端·面试·typescript
Orange_sparkle9 小时前
langgraph快速入门
学习·ai·typescript·deepseek·claude code
FYKJ_20101 天前
springboot刑事案件管理系统03047-计算机课程设计、毕业设计
vue.js·spring boot·python·mysql·typescript·spark·django
用户0934077735141 天前
HarmonyOS WPS Open SDK 实践:从 HAR 集成到 OpenFileRequest 最小闭环
typescript·harmonyos
FYKJ_20101 天前
springboot雅集社区养老管理系统04456-计算机课程设计、毕业设计
vue.js·spring boot·python·mysql·typescript·spark·django
FYKJ_20101 天前
express皖美特色农产品网售系统53118-计算机课程设计、毕业设计
javascript·vue.js·spring boot·mysql·typescript·spark·express
FYKJ_20101 天前
springboot羽毛球场地管理系统00626-计算机课程设计、毕业设计
vue.js·spring boot·python·mysql·typescript·spark·django
FYKJ_20101 天前
django药饮个性化推荐系统54839-计算机课程设计、毕业设计
vue.js·spring boot·python·mysql·typescript·spark·django
濮水大叔2 天前
Cabloy全栈框架的两个SSR入口:Vona集成式SSR vs Zova独立式SSR
typescript·node.js·全栈