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

从一个真实场景说起

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

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

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

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:揭开工具类型的"底牌"

有了 keyofExcludePick 三件套,我们就能自己实现 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,因为类型层面需要一个"值"才能推导出它的返回类型。

相关推荐
mmsx1 小时前
一个黑边 Bug 修了两版:自己算矩阵直接黑屏,借库重建只用了一行 setZoom
android·前端
丑过三八线1 小时前
001 -【FastAPI 入门教程】 FastAPI Helloword
前端·chrome·fastapi
禁止摆烂_才浅1 小时前
Axios 高频面试题
前端·面试·axios
何时梦醒1 小时前
TypeScript 工具类型一篇讲透:Pick、Omit、Partial、Exclude、Record、ReturnType、keyof
前端·面试·typescript
用户2181697049301 小时前
Flutter (十九) Tabbar
前端
默_笙1 小时前
🍳 受控组件和非受控组件,我纠结了一整天,最后用"房东和租客"讲明白了
前端·javascript
缓冲中请稍后1 小时前
前端HTTP请求完全指南:从基础到LLM接口调用实战
前端·面试
半个落月1 小时前
React 受控组件与非受控组件详解:从输入框到表单校验
前端·react.js
BreezeJiang1 小时前
写了 display:flex,为什么三栏布局还没完成?
前端·css