TypeScript 工具类型:像拼积木一样玩转类型

从一个真实场景说起

想象你在一家大公司做后台管理系统,数据库里有一张用户表:

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

问题来了:不同的页面需要的字段不一样。列表页只需要 id 和 name;编辑页只需要 name 和 age;完整信息页才需要全部字段。难道要为每个页面重新定义一套接口吗?

ts 复制代码
interface UserListItem { id: number; name: string; }
interface UserEditItem { name: string; age: number; }
interface UserFull { id: number; name: string; age: number; email: string; }

代码越写越多,一旦用户表加了一个字段,所有相关接口都要跟着改,这就是典型的"类型爆炸"。TypeScript 早就想好了对策:它提供了一组工具类型(Tool Types),让你可以在已有类型的基础上加工出新类型,就像用同一套积木拼出不同的造型。

Pick:从类型里"挑选"字段

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

Pick 的语法是 Pick<来源类型, '要保留的字段联合'>。这里的 'id' | 'name' 叫联合类型(Union Type) ,意思是"id 或者 name"。Pick 会把 User 中这两个字段挑出来,拼成一个全新类型,列表页直接就能用:

ts 复制代码
const u: UserPreview = { id: 1, name: '祖豪' }; // 不需要 email,类型层面就保证了

Omit:反过来,把不要的字段"删掉"

如果字段很多,只想去掉一两个,用 Pick 就得把所有保留字段写一遍,太啰嗦。这时轮到 Omit:

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

意思很直白:从 User 里去掉 email 字段,剩下的全保留 。email 属于敏感信息,用 Omit 排除后,从类型层面就杜绝了"不小心把 email 发给别人"的可能。

keyof:把对象变成"钥匙串"

要理解工具类型内部是怎么工作的,先认识 keyof。它能取出对象类型的所有键,并拼成联合类型:

ts 复制代码
type UserKeys = keyof User; // 'id' | 'name' | 'age' | 'email'

看,User 里的每个字段名都变成了一把"钥匙",串在了一起。

Exclude:从联合类型里"剔除"

Exclude 处理的对象不是对象类型,而是联合类型:

ts 复制代码
type AfterExclude = Exclude<'id' | 'name' | 'age' | 'email', 'email'>;
// 结果: 'id' | 'name' | 'age'

语义非常清楚:把第二个参数从第一个参数中剔除,剩下的原样返回。注意它俩的分工:Exclude 操作联合类型,Omit 操作对象类型,这是最本质的区别。

手写一个 Omit:揭开工具类型的"底牌"

有了 keyof、Exclude、Pick 三件套,我们就能自己实现 Omit 了------这正是 2.ts 演示的思路:

ts 复制代码
type MyOmitUser = Pick<User, Exclude<keyof User, 'email'>>;

一步步拆解:

  1. keyof User → 拿到所有键:'id' | 'name' | 'age' | 'email'
  2. Exclude<..., 'email'> → 把 email 剔除,剩下 'id' | 'name' | 'age'
  3. Pick<User, 剩下的键> → 从 User 里挑出剩余字段,得到没有 email 的新类型

最终结果和 Omit<User, 'email'> 完全一致。**工具类型不是魔法,它们就是普通类型操作符的组合。**这正是 TypeScript 最强大的地方------类型本身也是一种"数据",可以被计算、被组合。打开官方源码你会发现,Omit 的实现就是这个思路。

Partial:让所有字段都可选

接口更新(Patch)是开发中的高频操作,但用户可能只改一个字段,总不能每次请求都带上全部字段:

ts 复制代码
type PartialUser = Partial<User>; // 所有字段都变成可选

const patchUser: PartialUser = { name: '明明', age: 18 }; // 合法
const emptyObj: PartialUser = {};                          // 也合法

Partial 会给每个字段都加上问号。要注意:可选字段的底层类型其实是 T | undefined,使用前一定要做空值判断,否则容易踩坑。

Record:快速生成键值映射

后台里"状态码 → 错误提示"是最经典的应用场景:

ts 复制代码
type ErrorMsgMap = Record<number, string>;
const errorMessage: ErrorMsgMap = {
  400: '请求参数错误',
  404: '资源找不到',
  500: '服务器内部错误',
};
function getErrMsg(code: number): string {
  return errorMessage[code] ?? '未知错误';
}

Record<键类型, 值类型> 直接声明"键是 number、值是 string"的对象。再配合 ??(空值合并运算符),取值时就能优雅地兜底,不用写一堆 if-else。

ReturnType:白嫖函数的返回类型

函数的返回类型明明写在函数体里,但如果你想在别处复用这个类型,手动抄一遍既麻烦,又容易和实现脱节。ReturnType 帮你"偷懒":

ts 复制代码
function fn() { return { x: 1, y: 2 }; }
type fnReturn = ReturnType<typeof fn>; // { x: number; y: number }

注意参数是 typeof fn,因为类型层面需要一个"值"才能推导出它的返回类型。

相关推荐
福兮说10 小时前
前端下载大文件,点了按钮半天没反应:fetch + blob 的三个问题和三种替代写法
前端·javascript·性能优化·文件下载
GreenTea10 小时前
我把 Agent 的 while 循环拆掉了:一种你可能没想到的 Agent 架构
前端·后端·架构
科技观察哨11 小时前
六足平台选型与纳米定位系统集成:HEB-640六自由度位移台在半导体光刻对准中的参数边界与国产替代评估
前端·人工智能
niucloud-admin11 小时前
JAVA V6 多商户商城 开发文档——web前端
前端
颜进强11 小时前
16 · NestJS LifecycleEvents 生命周期事件:五个钩子、三条边界,和 rag-server 的优雅停机
前端·后端·ai编程
今年下半年13 小时前
【VUE】整合腾讯地图、自定义区域边界、村委名称及资产统计(放大显示资产点位)
前端·javascript·vue.js
郑州光合科技余经理13 小时前
同城电商系统:库存变更怎么同步到订单
java·开发语言·前端·后端·uni-app·php·ai编程
前端snow15 小时前
ai agent --- DeepAgents 中间件
前端
data analyse 45615 小时前
报表能自定义的国内统计工具有哪些
前端·数据分析