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,因为类型层面需要一个"值"才能推导出它的返回类型。

相关推荐
中文呀22 分钟前
三维 GIS 离线缓存实战:逆向 CesiumOfflineCache,并亲手为它补上"自动更新"能力
前端
前进前进前进1 小时前
TRAE 全链路实战:做了一个警示牌一键生成器:纯 Canvas、单文件、零依赖
前端
小万7422 小时前
God‘s Eye View 深度解析:15 路公开信号 + CesiumJS 的浏览器级 OSINT 控制台,60 帧渲染的“真实间谍卫星模拟器”
前端
IT星宿2 小时前
浏览器标签页全是一个图标:我用名字哈希生成 favicon,让每个项目都有身份标识
前端
全栈项目管理程序猿2 小时前
ArcGIS JS 基础教程(32):体元系列 - VoxelSlice 体元切片
前端·javascript
拖孩4 小时前
AI 帮我写的小程序「厨菜记」上线满月了,挣了两百块!!!
前端·后端·微信小程序
用户360055579004 小时前
Kestrel_VLLM - 13 · 为什么排除 MoE FFN Tiling 与 Tensor Core
前端
Hilaku5 小时前
AI 流式输出都用 SSE,什么时候必须换 WebSocket?
前端·javascript·程序员
奇牙coding5 小时前
Codex CLI 升级后多文件编辑静默失效:config.yaml 枚举变更排查与修复指南
java·前端·javascript·ai
小羊没烦恼!5 小时前
Ajax与JSON的一些总结
android·java·前端·数据库·mysql·算法·okhttp