- 文档
- 教程
【免费下载链接】typescript-book
The Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.
导读
本文以《The Concise TypeScript Book》开源仓库中 印尼语版 mapped-types 章节 为骨架,系统讲解 TypeScript Mapped Types(映射类型)的完整语法体系:从keyof属性遍历、T[P]索引访问、readonly/?修饰符,到键重映射(as子句)与条件类型、模板联合类型的组合运用。读完本文,你将掌握如何用映射类型把"信息相同、形态不同"的类型变换逻辑固化进类型系统,并能自行实现Readonly、Partial这类内置工具类型的底层原理。
一、什么是 Mapped Type:以类型变换类型
Mapped Type 是 TypeScript 中一类基于"既有类型"创建"新类型"的语法结构。它的核心思想是:不手工重写每个属性,而是通过一个映射函数遍历既有类型的每一个属性,逐一变换后生成新类型。映射既有类型,就能创建出"表达相同信息、但采用不同格式"的全新类型。
要创建一个映射类型,需要两步:
- 使用
keyof操作符取出既有类型的所有属性键; - 对每个键
P,通过索引访问T[P]取回原属性类型,再对其进行变换。
这一节对应 英文原版 mapped-types.md 与 印尼语版 mapped-types.md 开篇的定义,两者表述一致:mapped type 通过映射函数变换既有类型的每个属性。
二、第一个映射类型:让每个属性变成数组
原文档给出了一个完整可运行的最小示例,此处原样继承并逐行拆解:
type MyMappedType<T> = { [P in keyof T]: T[P][]; }; type MyType = { foo: string; bar: number; }; type MyNewType = MyMappedType<MyType>; const x: MyNewType = { foo: ['hello', 'world'], bar: [1, 2, 3], };逐行解读:
[P in keyof T]:keyof T返回MyType的属性键联合类型"foo" | "bar",P in ...表示对每个键进行一次映射迭代,P在此充当类型层面的循环变量;T[P][]:T[P]是索引访问(indexed access),取回string与number,追加[]后得到string[]与number[];- 因此
MyNewType展开后等价于{ foo: string[]; bar: number[]; },与MyType表达的信息相同,只是每个属性的形态从标量变成了数组; - 赋值时
foo: ['hello', 'world']、bar: [1, 2, 3]与映射结果完全吻合,类型检查通过。
这个例子的价值在于展示了映射类型的三个基本构件——键遍历(P in keyof T)、属性类型访问(T[P])、属性类型变换([])。它们是后续所有高级用法的地基。
三、底层构件:keyof、索引签名与索引访问
要真正理解映射类型,需要先厘清它依赖的两个相邻特性。仓库中 index-signatures.md 说明了索引签名可用string、number、symbol三种键类型;type-indexing.md 则演示了用索引签名定义"键未知"的类型:
type Dictionary<T> = { [key: string]: T; }; const myDict: Dictionary<string> = { a: 'a', b: 'b' }; console.log(myDict['a']); // Returns a映射类型的[P in keyof T]与索引签名语法上相似(都使用方括号),但语义不同:索引签名声明"未知键的值类型",映射类型则声明"对既有类型每个已知键的逐个变换"。keyof负责把键集合取出来,而T[P]负责按索引取回属性类型,二者组合构成了映射的"遍历 + 取值"机制。在 type-manipulation.md 的 Indexed Access Types 一节中也可以看到这种索引访问的独立用法:
type Person = { name: string; age: number; }; type AgeType = Person['age']; // number四、映射类型修饰符:readonly、+readonly、-readonly 与 ?
映射类型的威力在于可以对每个属性施加修饰符,从而批量改变属性的可变性。仓库 mapped-type-modifiers.md(含 印尼语版)列出了三种核心修饰符:
readonly(或显式写+readonly):把映射结果中的每个属性标记为只读;-readonly:去掉只读标记,让每个属性恢复可变(mutable);?:把每个属性标记为可选(optional)。
原文档示例:
type ReadOnly<T> = { readonly [P in keyof T]: T[P] }; // All properties marked as read-only type Mutable<T> = { -readonly [P in keyof T]: T[P] }; // All properties marked as mutable type MyPartial<T> = { [P in keyof T]?: T[P] }; // All properties marked as optional三个工具类型对应三种常见批量操作:
| 工具类型 | 语法 | 效果 |
|---|---|---|
ReadOnly<T> | { readonly [P in keyof T]: T[P] } | 所有属性变为只读 |
Mutable<T> | { -readonly [P in keyof T]: T[P] } | 移除只读标记,属性可变 |
MyPartial<T> | { [P in keyof T]?: T[P] } | 所有属性变为可选 |
这里体现了修饰符的加减法规则:+前缀表示"加上该修饰符"(readonly与?均可加),-前缀表示"移除该修饰符"(-readonly、-?)。readonly不带符号时等价于+readonly,?同理等价于+?。由于修饰符可加可减,映射类型不仅能"变严格",还能"变宽松"——比如把一个来自Readonly类型或as const对象的结果重新变回可写。
五、键重映射:用 as 子句 + 模板字面量改写键名
除变换属性值类型与修饰符外,TypeScript 4.1 起映射类型支持对键本身进行重命名。语法是在in子句后追加as,配合模板字面量类型(template literal types)生成新键名。仓库 exploring-the-type-system.md 的 Type Assertion 一节给出了如下示例:
type J<Type> = { [Property in keyof Type as `prefix_${string & Property}`]: () => Type[Property]; }; type X = { a: string; b: number; }; type Y = J<X>;解读:
as \prefix_${string & Property}`:把键"a"、"b"重映射为"prefix_a"、"prefix_b"`;string & Property:是模板字面量类型的约束写法——模板插值要求类型可序列化为字符串,将Property与string取交集可让编译器确信键名是字符串类型;- 值类型
() => Type[Property]:新属性的值是从原属性类型派生的函数类型。
展开后Y等价于{ prefix_a: () => string; prefix_b: () => number; }。键重映射最常见的场景是:给一组字段统一加前缀/后缀、把键转换为getXxx/setXxx形式的访问器名、或配合条件类型在映射中过滤掉某些键(当as结果为never时该键被剔除)。
六、与其他类型操作组合:条件类型与模板联合类型
映射类型不是孤立特性,它与条件类型、模板联合类型组合后能表达相当复杂的类型变换逻辑。
组合条件类型做键过滤。仓库 conditional-types.md 展示了条件类型的基础形态:
type IsArray<T> = T extends any[] ? true : false;把extends三元判断放进as重映射中,即可实现"只保留满足条件的键":
type FilterStringKeys<T> = { [P in keyof T as T[P] extends string ? P : never]: T[P]; };组合模板联合类型做键加工。仓库 template-union-types.md 演示了模板字面量类型如何批量生成字符串联合:
type Status = 'active' | 'inactive'; type Products = 'p1' | 'p2'; type ProductId = `id-${Products}-${Status}`; // "id-p1-active" | "id-p1-inactive" | "id-p2-active" | "id-p2-inactive"id-${Products}-${Status}展开为 4 个具体字符串字面量的联合。在as子句中用同样的插值语法对keyof T的每个键做重映射,正是第五节prefix_${...}示例的通用形式。
七、映射类型与内置 Utility Types 的关系
理解了映射类型语法后会发现,TypeScript 大量内置工具类型本质上就是映射类型的"官方预置实现"。仓库 type-manipulation.md 的 Utility Types 一节列举了常用的工具类型,其中几个与本文主题直接对应:
type A = Partial<Person>; // { name?: string | undefined; age?: number | undefined; } type A = Required<Person>; // { name: string; age: number; } type A = Readonly<Person>;Partial<T>≈ 本文MyPartial<T>:{ [P in keyof T]?: T[P] };Required<T>≈{ [P in keyof T]-?: T[P] }:通过-?移除可选标记;Readonly<T>≈ 本文ReadOnly<T>:{ readonly [P in keyof T]: T[P] };Record<K, T>:等价于"以键联合K生成每个属性类型都是T"的映射({ [P in K]: T }),例如:
type Product = { name: string; price: number }; const products: Record<string, Product> = { apple: { name: 'Apple', price: 0.5 }, banana: { name: 'Banana', price: 0.25 }, };Pick<T, K>、Omit<T, K>:本质也是映射类型加键过滤的变体——Pick只映射K中的键,Omit通过as重映射为never剔除K。
也就是说,掌握[P in keyof T]与修饰符加减法后,你可以亲手还原出这些标准工具类型,也就能理解它们在底层是如何工作的,进而写出自己的定制版本。
八、典型实战场景小结
结合本章节与仓库上下文,映射类型在真实项目中的高价值场景包括:
- 批量不可变化:对接口返回的 DTO 统一加
readonly,防止意外改写; - 表单/配置二态化:用
Partial<T>表示"部分字段已填",用Required<T>表示"全部必填"; - 键名工程化:用
as+ 模板字面量生成get_${key}风格的访问器类型或id_${key}风格的事件类型; - 按条件过滤字段:结合条件类型只保留字符串字段、函数字段等特定类型的键;
- 状态机约束:从字段联合生成"每个字段一组状态"的完整类型空间,与 discriminated-unions.md、union-type.md 讨论的可辨识联合配合使用。
九、总结
Mapped Type 是 TypeScript 类型系统中"元编程"能力的核心入口:[P in keyof T]提供遍历,T[P]提供取值,readonly/?及+/-修饰符提供批量属性调整,as子句配合模板字面量提供键级重映射,再与条件类型、联合类型组合即可表达"以类型变换类型"的完整逻辑。本文内容以 印尼语版 mapped-types.md 与 英文原版 mapped-types.md 为主体,深度拓展自仓库中的 mapped-type-modifiers.md、type-manipulation.md、exploring-the-type-system.md、conditional-types.md、template-union-types.md、index-signatures.md 与 type-indexing.md。读者可继续在上述章节中探索交集类型、条件类型、工具类型等相邻主题,构建完整的类型操作知识体系。
- 文档
- 教程
【免费下载链接】typescript-book
The Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.
相关推荐
The Concise TypeScript Book 精读:Mapped Types(映射类型)——用 `keyof` 批量转换现有类型的属性
The Concise TypeScript Book 精读:Mapped Types(映射类型)——用 keyof 批量转换现有类型的属性 映射类型(Mapp
文档教程《The Concise TypeScript Book》精读:Mapped Types 映射类型完全指南
《The Concise TypeScript Book》精读:Mapped Types 映射类型完全指南 映射类型(Mapped Types)是 TypeSc
文档教程The Concise TypeScript Book:映射类型(Mapped Types)实战指南
The Concise TypeScript Book:映射类型(Mapped Types)实战指南 映射类型是 TypeScript 类型系统中最强大的类型变
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考