【Typescript】09-映射类型与内置工具类型
映射类型与内置工具类型
如果说泛型解决的是“一个类型模板如何适配不同输入”,那么映射类型解决的就是另一类高频问题:如何基于一个已有类型,系统性地批量改造出另一个类型。
这在真实项目里非常重要。因为几乎没有哪个业务模型只会以一种形态存在。一个 User 往往会派生出:
- 新建表单输入类型
- 更新接口类型
- 列表项类型
- 对外返回类型
- 只读展示类型
- 字段权限映射
如果你每次都手写一份新结构,不仅重复,而且极易失去同步。映射类型的价值,就是把这些“结构性变化”变成可复用的类型操作。
什么是映射类型
先看一个最基础的例子:
type Optional<T> = {
[K in keyof T]?: T[K];
};
这段代码的意思是:
- 遍历
T的每一个键 - 用
K依次拿到这些键 - 对每个键对应的属性类型保持不变
- 但把所有属性都改成可选
如果有:
interface User {
id: number;
name: string;
active: boolean;
}
那么 Optional<User> 的结果就是:
{
id?: number;
name?: string;
active?: boolean;
}
这就是映射类型的本质:按某种规则,批量遍历并改造一个类型的字段。
为什么映射类型这么适合真实业务
因为业务模型通常不是孤立存在的。比如一个用户模型:
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: string;
}
它在不同场景里会有不同需求:
- 创建用户时不该由前端传
id - 更新用户时很多字段可以局部提交
- 返回前端时不该暴露
password - 展示详情时可能全字段只读
如果每种场景都重新写一个接口,代码会越来越重复。映射类型和工具类型,就是为这种“基于一个源模型派生多个视图”的需求而生的。
TypeScript 内置工具类型,本质上就是常用映射类型的封装
最常见的工具类型包括:
Partial<T>:把所有字段变成可选Required<T>:把所有字段变成必填Readonly<T>:把所有字段变成只读Pick<T, K>:挑选部分字段Omit<T, K>:排除部分字段Record<K, V>:按键集合构造对象类型
先看两个非常实用的例子:
type CreateUserInput = Omit<User, "id" | "createdAt">;
type UpdateUserInput = Partial<Omit<User, "id" | "createdAt" | "password">>;
这两行已经足够覆盖很多业务接口设计。
Partial、Required、Readonly 的典型使用场景
Partial<T>
type UpdateUserInput = Partial<User>;
它适合局部更新、表单 patch 提交、配置覆盖等场景。含义非常直接:这个对象不一定一次性提供全部字段。
Required<T>
type CompleteUser = Required<UserDraft>;
它常用于“草稿阶段字段可缺失,正式提交时字段必须完整”的场景。
Readonly<T>
type UserView = Readonly<User>;
这在缓存快照、只读配置、不可变状态管理中很常见。它能帮助你表达“这个对象是给你看的,不是给你改的”。
Pick 和 Omit 是实际项目中极高频的组合工具
假设你有:
interface User {
id: number;
name: string;
email: string;
password: string;
createdAt: string;
}
那么:
type UserPreview = Pick<User, "id" | "name" | "email">;
type PublicUser = Omit<User, "password">;
这类写法特别适合:
- 列表页只展示部分字段
- 接口响应排除敏感字段
- 组件 props 只消费原模型的一部分
它们最大的价值不是省几行代码,而是明确告诉读者:这个类型是从哪个源模型裁剪出来的。
Record 是很多人低估的利器
type Permission = "read" | "write" | "delete";
const permissionLabel: Record<Permission, string> = {
read: "读取",
write: "写入",
delete: "删除"
};
Record 的好处在于,它把“对象键必须完整覆盖某个联合类型”这件事变成了编译期约束。你只要漏写一个键,TypeScript 就会直接提醒你。
这在这些场景特别有用:
- 角色到文案映射
- 状态到组件映射
- 权限到处理函数映射
- 枚举值到显示标签映射
相比写一个普通对象,Record 更能表达“这里不是随便几个键,而是一个必须完整覆盖的映射关系”。
你也可以自己写工具类型
前面提到的 Optional<T> 就是最简单的自定义工具类型。再看两个常见写法:
type Mutable<T> = {
-readonly [K in keyof T]: T[K];
};
type Concrete<T> = {
[K in keyof T]-?: T[K];
};
这里的:
-readonly表示去掉只读修饰符-?表示去掉可选修饰符
你不一定要一开始就背熟这些语法,但你要知道一件事:内置工具类型不是神秘黑盒,它们本质上就是少量映射能力的组合。
映射类型真正厉害的地方,是它在表达“派生关系”
这是非常重要的一层理解。很多人学工具类型时,只记成几个 API 名字,比如 Pick、Omit、Partial。但你如果只记名字,很快就会忘。
更好的理解方式是:
Partial表达“同一模型的可选版本”Readonly表达“同一模型的只读版本”Pick表达“从源模型中投影一部分字段”Omit表达“从源模型中排除某些字段”Record表达“从键集合到值类型的完整映射”
一旦你从“语法”切换到“派生关系”,这些工具会变得非常自然。
一个真实建模例子
interface Article {
id: number;
title: string;
content: string;
authorId: number;
status: "draft" | "published";
createdAt: string;
}
type CreateArticleInput = Omit<Article, "id" | "createdAt">;
type UpdateArticleInput = Partial<Omit<Article, "id" | "createdAt">>;
type ArticleCard = Pick<Article, "id" | "title" | "status">;
type ReadonlyArticle = Readonly<Article>;
从这个例子你可以看到,整个系统围绕一个核心模型派生出多个场景类型,而不是每个场景都独立抄一份结构。这样后续演化时,维护成本会低很多。
一个工程建议:先用内置工具,不要急着重复造轮子
很多初学者学会映射类型后,会开始写各种自定义工具。技术上当然可以,但工程上不一定划算。
更稳妥的顺序应该是:
- 先熟练掌握内置工具类型
- 只有当内置工具无法表达需求时,再自己写映射类型
- 自定义工具类型命名要清楚,别为了省几行代码引入更重的理解成本
因为大多数业务场景,其实已经被官方工具覆盖得差不多了。
初学者常见误区
误区一:派生类型全部手写
短期看快,长期看最容易失控。字段一变,五六处都得手动同步。
误区二:看见映射类型就觉得太高级,不敢用
其实你每天都可能在写它的结果,只是还没意识到这些“局部可选”“字段裁剪”本来就很适合自动化。
误区三:为了高级而写过度抽象的自定义工具
工具类型的目标是减少重复和提升清晰度,不是把简单需求改写成阅读门槛更高的类型体操。
本文小结
映射类型让你可以基于一个已有类型,按规则批量生成另一个类型;内置工具类型则是这些常见规则的现成封装。掌握它们之后,你写类型的方式会从“复制粘贴多个相似接口”,升级为“围绕核心模型系统派生多个场景类型”。
这不仅更省代码,更重要的是,它让类型之间的关系更清楚、更稳定、更适合长期维护。
练习
- 用
Pick从User中挑出id和name,再解释为什么这比手写一个新接口更好维护。 - 用
Omit构造一个不包含password的返回类型,并为用户列表页再派生一个精简展示类型。 - 用
Record定义一份角色到中文名称的映射,再试着漏掉一个键,观察 TypeScript 的提示。
后记
2026年5月21日于上海。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)