- 文档
- 教程
【免费下载链接】typescript-book
The Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.
本篇指南围绕 The Concise TypeScript Book(本仓库website/src/content/docs/pt-br/book/mapped-type-modifiers.md所讲解的章节)的核心主题展开:TypeScript 映射类型(Mapped Types)中的三类修饰符——readonly/+readonly(只读)、-readonly(取消只读)、?(可选)——是如何驱动类型转换的。读者学完后将掌握用映射类型修饰符构造只读、可变、可选类型的标准写法,理解其在Partial、Readonly等内置工具类型中的实现原理,并能在实际项目中安全地应用和扩展这些模式。
1. 从映射类型说起:修饰符的作用对象
要理解修饰符,首先要明确它们“修饰”的是什么。TypeScript 的映射类型允许你基于一个已有类型,用keyof取出其全部属性键,再逐一对每个属性进行变换,生成一个新类型。映射类型的骨架如下:
type MyMappedType<T> = { [P in keyof T]: T[P][]; };这里[P in keyof T]就是“映射”动作本身:P遍历T的所有键,T[P]通过索引访问取得每个属性的原类型。上面的例子把每个属性的类型改成了“原类型的数组”。比如:
type MyType = { foo: string; bar: number; }; type MyNewType = MyMappedType<MyType>; const x: MyNewType = { foo: ['hello', 'world'], bar: [1, 2, 3], };MyNewType与MyType表达同样的信息(键名不变),但每个值的形态被转换成了数组。映射类型修饰符正是在这个“逐属性变换”的骨架之上,额外叠加readonly、?等标记,对每个属性的可变性与可选性进行批量改写。想深入了解映射类型本身,可阅读本仓库的 mapped-types.md 章节。
2. 三大修饰符的语义
本节对应原文档的核心要点:readonly(含+readonly)、-readonly与?。它们的共同点是都写在映射类型的花括号内部、属性声明的位置上,作用于被遍历到的每一个属性。
| 修饰符 | 写法位置 | 作用 |
|---|---|---|
readonly/+readonly | 属性键之前 | 将映射后的属性标记为只读,禁止重新赋值 |
-readonly | 属性键之前 | 移除只读标记,使属性恢复可写(可变) |
? | 属性键之后 | 将属性标记为可选(可缺省) |
说明:本文“属性键”指
[P in keyof T]这段映射表达式本身,例如readonly [P in keyof T]: T[P]中readonly位于映射表达式之前。
需要特别区分的是:?修饰符在原文档中没有给出显式的-?写法(-?表示移除可选标记,是Required<T>工具类型的底层实现,属 TS 4.1 引入的能力)。以当前章节文档为准,我们聚焦?的“添加可选”语义。
3. 原文档代码示例:只读、可变、可选三种变换
原文档(mapped-type-modifiers.md)给出三个可直接运行的类型定义:
type ReadOnly<T> = { readonly [P in keyof T]: T[P] }; // 所有属性标记为只读 type Mutable<T> = { -readonly [P in keyof T]: T[P] }; // 所有属性标记为可变 type MyPartial<T> = { [P in keyof T]?: T[P] }; // 所有属性标记为可选逐一拆解:
ReadOnly<T>:对T的每个属性都加上readonly,得到一个所有属性均不可重新赋值的类型。实际使用时:
type Person = { name: string; age: number }; type ReadonlyPerson = ReadOnly<Person>; const p: ReadonlyPerson = { name: 'Simon', age: 17 }; p.name = 'John'; // 编译错误:无法分配给只读属性 'name'Mutable<T>:-readonly是“移除只读”的显式写法。它常用于把某个已被冻结(readonly)的类型重新开放为可写。即便输入类型本没有readonly属性,结果也保持一致(对非只读属性移除只读是无操作)。示例:
type FrozenConfig = { readonly host: string; readonly port: number }; type WritableConfig = Mutable<FrozenConfig>; // 结果类型:{ host: string; port: number }MyPartial<T>:对所有属性加上?,等价于内置的Partial<T>。它常用于参数对象“全部可缺省”的场景:
type PartialPerson = MyPartial<Person>; // 结果类型:{ name?: string; age?: number } function updatePerson(p: PartialPerson) { /* 只更新传入的字段 */ }4. 结合仓库源码的纵深剖析
4.1 与内置工具类型的关系
MyPartial<T>实际就是手写的Partial<T>;ReadOnly<T>即手写的Readonly<T>。在 TypeScript 的标准库中:
Partial<T>={ [P in keyof T]?: T[P] }Readonly<T>={ readonly [P in keyof T]: T[P] }Required<T>={ [P in keyof T]-?: T[P] }
也就是说,原文档的示例正是在实现这些官方工具类型。本仓库的 type-manipulation.md 章节对Partial<T>、Required<T>、Readonly<T>等工具有系统讲解,例如Partial<Person>展开为{ name?: string | undefined; age?: string | undefined },Required<Person>将可选属性重新置为必选,Readonly<Person>则禁止后续赋值(a.name = 'John'属非法操作),可对照阅读。
4.2 仓库内真实使用佐证
当前仓库(The Concise TypeScript Book 的官方网站与构建代码)在自身代码中广泛使用这些模式,可作为“只读+映射”的实际应用样本:
- website/src/data/plusEdition.ts 中多处使用
Readonly<Record<string, ...>>声明配置对象,例如LOCALE_BY_LANG、PLUS_EDITION_COVERS、AMAZON_DOMAINS_BY_REGION均为Readonly<Record<...>>类型。这展示了“只读修饰符批量作用于 Record 的所有属性”这一组合:Record<K, T>本身是{ [P in K]: T }的映射类型,外层再套Readonly<...>即为双重映射。 - website/src/config/locales.ts 中
export type Locale = keyof typeof locales;是keyof取键的典型用例——它正是映射类型[P in keyof T]中“键集合”的来源,与修饰符示例中的keyof T一脉相承。
此外,仓库对?可选属性的基础语义也有专门章节:optional-properties.md 展示了b?: number的可选声明及解构默认值写法({ a, b = 100 }: X) => a + b;readonly-properties.md 则说明了readonly只能防止重写、不能保证深层不可变性,并给出Readonly<{ a: number }>与只读索引签名readonly [index: number]: string的写法。这两章共同补全了修饰符的“单属性”语义基础。
5. 修饰符与映射类型其他能力的组合
映射类型修饰符不是孤立的,它与本仓库其他章节介绍的映射能力经常搭配使用:
- 与索引访问类型组合:
T[P]本质是索引访问,见 type-indexing.md 与 type-manipulation.md 中的Person['age']示例。修饰符只改变属性的标记(readonly/optional),不改变值的类型。 - 与
+/-显式符号组合:+readonly与readonly等价,属显式“添加”写法;-readonly是“移除”。对于?,TS 同样支持-?(移除可选),本仓库的Required<T>讲解即属此列。 - 与键重映射(as)组合:
[P in keyof T as NewKey]可同时改名键并施加修饰符,例如生成“每个字段名加Id后缀且只读”的类型。该能力结合本仓库 template-union-types.md 中的模板字面量联合(如`id-${Products}-${Status}`)可用于构造键名。 - 与索引签名组合:
readonly [index: number]: string这样的只读索引签名见 readonly-properties.md;索引签名的键类型可为string | number | symbol,见 index-signatures.md。
6. 常见陷阱与实战建议
readonly不保证深层不可变:Readonly<T>只冻结顶层属性的重新赋值;若属性值是对象或数组,其内部仍可变。需要递归只读时应自行递归映射(可结合条件类型),或采用第三方 deep-readonly 方案。-readonly只在映射类型中合法:它不能用于普通的对象类型字面量(如{ -readonly a: number }是非法的),只能出现在[P in keyof T]这样的映射语境中。?的级联效应:用MyPartial<T>得到的可选属性,其类型在严格模式下为T[P] | undefined;访问前需做空值判断,或配合解构默认值(见 optional-properties.md)。- 优先复用内置工具类型:当目标就是“全部只读”“全部可选”“全部必选”时,直接用
Readonly<T>、Partial<T>、Required<T>更符合惯例;自定义修饰符类型适合在需要“部分键”或“键重命名”等定制场景使用。 - 多修饰符可叠加:
{ readonly [P in keyof T]?: T[P] }能同时得到“只读且可选”的属性;对既有类型再套Mutable可以“解冻”之前套上的Readonly。
7. 小结
映射类型修饰符是 TypeScript 类型体操中“批量改写属性标记”的利器:readonly/+readonly批量冻结,-readonly批量解冻,?批量转可选。原文档给出的三个手写类型(mapped-type-modifiers.md)与内置的Readonly<T>、Mutable(等价于移除Readonly)、Partial<T>一一对应,而本仓库的 type-manipulation.md、readonly-properties.md、optional-properties.md、mapped-types.md 各章节从不同角度印证了这些模式,website/src/data/plusEdition.ts 与 website/src/config/locales.ts 则展示了Readonly<Record<...>>与keyof在真实项目中的落地用法。掌握这三个修饰符,就掌握了在类型层面统一控制“只读性”与“可选性”的核心手段,可以进一步向键重映射、递归只读等高阶类型体操延伸。
- 文档
- 教程
【免费下载链接】typescript-book
The Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.
相关推荐
TypeScript 映射类型修饰符(Mapped Type Modifiers)完全指南:readonly、+readonly、-readonly 与可选性变换
TypeScript 映射类型修饰符(Mapped Type Modifiers)完全指南:readonly、+readonly、 readonly 与可选性变
文档教程TypeScript 映射类型修饰符(Mapped Type Modifiers)实战指南:readonly、-readonly 与可选属性 `?`
TypeScript 映射类型修饰符(Mapped Type Modifiers)实战指南:readonly、 readonly 与可选属性 ? 本指南以开源仓
文档教程TypeScript 映射类型修饰符(Mapped Type Modifiers)实战:readonly、可变性与可选性的精确变换
TypeScript 映射类型修饰符(Mapped Type Modifiers)实战:readonly、可变性与可选性的精确变换 导读 本文围绕开源仓库 Th
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考