实战:readonly、可变性与可选性的精确变换)
文档教程【免费下载链接】typescript-bookThe Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.项目地址https://gitcode.com/gh_mirrors/typ/typescript-book点击查看免费下载导读本文围绕开源仓库 The Concise TypeScript Book意大利语译本源码文档英文原版见 mapped-type-modifiers.md中“映射类型修饰符”一章展开。你将系统掌握在映射类型{ [P in keyof T]: ... }中如何用readonly或readonly、-readonly、?与-?精准控制新类型的只读性、可变性与可选性并能据此理解ReadonlyT、PartialT、RequiredT等内置工具类型的底层实现从而在自己的项目里写出可复用的类型变换工具。一、前置知识映射类型与keyof运算符映射类型修饰符不是孤立的语法它总是作用在“映射类型”之上。所谓映射类型就是“基于一个已有类型通过映射函数逐属性变换生成一个表示相同信息、但形态不同的新类型”。The Concise TypeScript Book 的 Mapped Types 章节给出了最直观的例子type MyMappedTypeT { [P in keyof T]: T[P][]; }; type MyType { foo: string; bar: number; }; type MyNewType MyMappedTypeMyType; const x: MyNewType { foo: [hello, world], bar: [1, 2, 3], };这里的关键语法是keyof T取出类型T的全部属性名键组成一个联合类型[P in keyof T]遍历这些键P依次代表每一个属性名T[P]索引访问类型取出属性P对应的值类型T[P][]把每个属性的值类型“包一层数组”。因此MyNewType与MyType拥有完全相同的属性集合只是每个属性的值从string变成了string[]、从number变成了number[]。映射类型修饰符Mapped Type Modifiers正是这套遍历语法上的“开关”它允许你在映射过程中额外控制每个属性是只读还是可写、是必选还是可选从而改变新类型的约束能力而不改变属性本身的类型。在本书的目录结构中该主题紧随映射类型之后第 38 章 Mapped Types 讲遍历与变换第 39 章 Mapped Type Modifiers 讲修饰符二者构成完整的学习路径见 Table of Contents。二、三种核心修饰符及其语义原文档明确指出TypeScript 的映射类型修饰符允许你变换既有类型内部属性的约束特性共涉及以下三种能力修饰符语义效果readonly或readonly把属性标记为只读映射后的新类型中该属性只能读取、不能重新赋值-readonly移除只读标记把原本只读的属性变为可变可写?把属性标记为可选映射后的新类型中该属性可以缺省三个修饰符的核心逻辑如下readonly/readonly添加只读约束。readonly本身是 TypeScript 对象类型上独立的属性修饰符详见本书 Readonly Properties当它出现在映射类型的遍历表达式里时会为每一个被遍历到的属性统一加上只读标记。readonly与readonly等价前缀显式表示“添加”。-readonly移除只读约束。-前缀表示“移除”。在映射类型中使用-readonly会把源类型中标记为readonly的属性“还原”为可写属性。这是把不可变模型转换为可变模型的核心手段。?添加可选约束。?是对象类型中标记可选属性的标准符号详见本书 Optional Properties在映射类型中放在属性名之后可为所有遍历到的属性统一加上可选标记。需要特别强调的是映射类型的输出只与“映射关系”相关这些修饰符统一作用于输出的新类型。同时readonly与?可以自由组合使用例如readonly可选属性它们在语义上互不冲突。三、原书示例逐行解读ReadOnly、Mutable、MyPartial原文档提供了三个可直接复制运行的完整示例它们分别对应“全部只读”“全部可变”“全部可选”三种最常见的变换诉求type ReadOnlyT { readonly [P in keyof T]: T[P] }; // 所有属性标记为只读 type MutableT { -readonly [P in keyof T]: T[P] }; // 所有属性标记为可变 type MyPartialT { [P in keyof T]?: T[P] }; // 所有属性标记为可选3.1ReadOnlyT一键生成不可变版本type ReadOnlyT { readonly [P in keyof T]: T[P] }; interface User { name: string; age: number; } type ImmutableUser ReadOnlyUser; const user: ImmutableUser { name: Alice, age: 30 }; user.age 31; // ❌ 错误无法为 age 赋值因为它是只读属性readonly出现在遍历表达式之前表示对keyof T遍历出的每一个属性统一加只读。这在“配置对象”“领域快照”等不希望被外部篡改的场景中非常实用——你只需把原始类型交给ReadOnlyT就能在编译期获得不可变保证。3.2MutableT把只读模型还原为可写模型type MutableT { -readonly [P in keyof T]: T[P] }; type FrozenUser ReadonlyUser; // 来自上文的只读版本 type EditableUser MutableFrozenUser; // 只读标记被移除 const editor: EditableUser { name: Bob, age: 25 }; editor.age 26; // ✅ 合法-readonly 已移除只读约束-readonly的价值在于当你在系统中收到一个“只读版本”的类型例如来自ReadonlyT或定义了readonly属性的接口却需要在某个内部流程中修改它时不需要手写一遍完整类型直接用MutableT即可“解锁”。注意-readonly只移除readonly标记并不会改变属性原本的类型与可选性。3.3MyPartialT让所有属性变为可选type MyPartialT { [P in keyof T]?: T[P] }; type PartialUser MyPartialUser; // 只提供部分字段也合法 const partial: PartialUser { name: Carol };?放在遍历表达式[P in keyof T]的右括号之后、值类型之前表示统一添加可选标记。这在“部分更新接口”“渐进式表单”等场景中几乎每天都会用到。三个示例组合起来恰好覆盖了修饰符的三种基本方向加只读、减只读、加可选。你还可以在同一映射类型中同时使用多个修饰符// 全部只读 全部可选典型的“查询快照”形态 type ReadonlyPartialT { readonly [P in keyof T]?: T[P] }; // 全部可写 全部必选典型的“写入命令”形态 type MutableRequiredT { -readonly [P in keyof T]-?: T[P] };四、扩展-?移除可选标记实现“反向”变换原文档只列出了“添加可选”的?。与之对称TypeScript 同样支持-?修饰符用于在映射时移除可选标记、把属性变为必选。这是完整的修饰符谱系中必不可少的一环type RequiredAllT { [P in keyof T]-?: T[P] }; interface Settings { theme?: string; locale?: string; } // 所有属性变为必选 type CompleteSettings RequiredAllSettings; const settings: CompleteSettings { theme: dark }; // ❌ 缺少 locale综合起来/-前缀可以作用在readonly与?两类修饰符上形成完整的 2×2 变换矩阵修饰符写法含义readonly或readonly添加只读标记-readonly移除只读标记?添加可选标记-?移除可选标记转为必选该矩阵的逻辑正是内置工具类型RequiredT的底层原理RequiredT本质上就是对T的每个属性应用-?并保留原有类型与只读性见下文第五节。五、与内置工具类型ReadonlyT、PartialT、RequiredT的对应关系The Concise TypeScript Book 在 Type Manipulation 章节的 Utility Types 一节中将PartialT、RequiredT、ReadonlyT列为最常用的内置工具类型。有了映射类型修饰符的知识你可以直接看懂它们的实现本质// ReadonlyT所有属性设为只读 —— 等价于 { readonly [P in keyof T]: T[P] } type A Readonly{ name: string; age: number }; // 结果类型{ readonly name: string; readonly age: number; } // PartialT所有属性设为可选 —— 等价于 { [P in keyof T]?: T[P] } type B Partial{ name: string; age: number }; // 结果类型{ name?: string | undefined; age?: number | undefined; } // RequiredT所有属性设为必选 —— 等价于 { [P in keyof T]-?: T[P] } type C Required{ name?: string; age?: number }; // 结果类型{ name: string; age: number; }因此当你在实际项目中需要“把接口的每个字段都变只读/可选/必选”时优先使用内置工具类型即可而当需求更复杂例如“只读但允许缺省”“只对某几个键生效”就可以基于映射类型修饰符自行编写定制工具。六、实战用映射类型修饰符解决真实问题6.1 场景一将领域模型“冻结”为不可变快照type ReadOnlyT { readonly [P in keyof T]: T[P] }; interface Order { id: string; amount: number; status: pending | paid | shipped; } function freezeT(value: T): ReadOnlyT { return value as ReadOnlyT; } const snapshot freezeOrder({ id: A001, amount: 99, status: paid }); snapshot.status shipped; // ❌ 只读属性不可赋值借助ReadOnlyT底层即readonly [P in keyof T]任何领域对象都能在编译期被保护起来防止状态在流转过程中被意外修改。6.2 场景二把“完整实体”变换为“更新载荷”type MyPartialT { [P in keyof T]?: T[P] }; interface Profile { name: string; email: string; bio: string; } function updateProfile(patch: MyPartialProfile): void { // 允许只传部分字段{ name: New Name } 或 { bio: ... } } updateProfile({ email: newexample.com }); // ✅ updateProfile({}); // ✅ 空对象也合法6.3 场景三接口定义全readonly内部处理时“解锁”// 外部契约只读 interface ApiUser { readonly id: number; readonly name: string; } // 内部处理解锁只读方便在流水线中逐步填充 type MutableT { -readonly [P in keyof T]: T[P] }; type DraftUser MutableApiUser; function buildUser(draft: DraftUser) { draft.name draft.name.trim(); // ✅ 可变 }这种“对外只读、对内可变”的建模方式是修饰符最典型的工程化应用。七、边界与陷阱理解readonly与?的真实约束7.1readonly不是深度不可变本书 Readonly Properties 明确指出readonly修饰符只能阻止属性被重新赋值并不能提供整体不可变性total immutability的保证。嵌套对象内部的字段依然可以被修改type J Readonly{ a: number; nested: { value: number } }; const obj: J { a: 1, nested: { value: 10 } }; obj.a 2; // ❌ 顶层只读 obj.nested.value 99; // ✅ 嵌套对象内部仍然可变如果确实需要深度不可变需要递归映射在值类型上继续应用Readonly或借助运行时冻结方案但那是超出本章范围的进阶话题。7.2 可选属性的默认值约定本书 Optional Properties 还介绍了一个易被忽略的细节可选属性配合解构可以指定默认值。同理MyPartialT变换出的可选属性在消费时也常用默认值兜底type X { a: number; b?: number; }; const x ({ a, b 100 }: X) a b;对于MyPartialT生成的类型解构默认值依然是安全的处理方式type MyPartialT { [P in keyof T]?: T[P] }; function totalPrice(item: MyPartial{ price: number; tax: number }) { const { price 0, tax 0 } item; return price tax; }7.3 只读索引签名readonly同样适用于索引签名。本书 Readonly Properties 中的示例展示了其写法type K { readonly [index: number]: string; };这意味着在映射类型之外readonly与?作为对象类型的通用修饰符本就可以独立使用映射类型只是把它们“批量化”应用到每个遍历到的属性上。7.4 使用前提与限制映射类型修饰符只影响属性的约束标记只读/可选不会改变属性的值类型-readonly与-?只能“移除”源类型中确实存在的标记对原本就没有该标记的属性无副作用幂等以上全部行为都是纯类型层面的编译后会完全擦除不产生任何运行时开销。八、在 The Concise TypeScript Book 中的位置与延伸阅读本文内容对应 The Concise TypeScript Book 第 39 章“映射类型修饰符”在 英文原版 与 意大利语译本 中均可找到完全一致的代码示例与语义说明全书的 Table of Contents 将其编排在“Mapped Types第 38 章→ Mapped Type Modifiers第 39 章→ Conditional Types第 40 章”的进阶链路上。推荐按以下顺序继续深入仓库中的相关章节Mapped Types掌握[P in keyof T]遍历语法本身Readonly Properties理解readonly的语义边界Optional Properties理解?与默认值约定Type Manipulation在 Utility Types 一节验证ReadonlyT、PartialT、RequiredT与本文修饰符的一一对应关系Index Signatures 与 Type Indexing补齐keyof、索引访问类型等前置能力。至此你已经完整掌握了映射类型修饰符的三种方向readonly/readonly、-readonly、?及其反向形态-?并能在实际项目中把“批量只读化”“批量可变化”“批量可选化/必选化”这类高频类型变换从内置工具类型直接迁移到自定义映射类型的实现中。赞分享文档教程【免费下载链接】typescript-bookThe Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.项目地址https://gitcode.com/gh_mirrors/typ/typescript-book点击查看免费下载相关推荐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、-readonly 与 ? 的实战剖析TypeScript 映射类型修饰符Mapped Type Modifiers完全指南readonly、 readonly 与 ? 的实战剖析 映射类型修文档教程上一篇Auto-Photoshop-StableDiffusion-Plugin5分钟在 Photoshop 里直接用 Stable Diffusion 出图下一篇EMQX 监听器绑定地址可移植性检查针对主机专属 IP 的告警日志机制深入解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考