《The Concise TypeScript Book》精讲:TypeScript 映射类型修饰符(Mapped Type Modifiers)完全指南
文档教程【免费下载链接】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点击查看免费下载映射类型Mapped Types允许我们基于既有类型逐属性生成新类型而映射类型修饰符Mapped Type Modifiers则是在这个过程中控制每个属性只读性与可选性的关键语法。本文以开源仓库 typ/typescript-book 中 mapped-type-modifiers.md 章节为骨架结合同书 mapped-types.md、readonly-properties.md、optional-properties.md 与 type-manipulation.md 等章节系统讲解readonly、-readonly、?三个修饰符的语法、语义与实战组合读完后你将能够手写ReadonlyT、PartialT等工具类型的等价实现并在自己的类型体系中自如地加锁与解锁属性。一、前置基础先理解映射类型本身在讨论修饰符之前必须先明确它们作用的载体——映射类型。按照本书 mapped-types.md 章节的定义Mapped Types 允许你基于一个既有类型通过映射函数逐属性变换创建出新类型。通过映射既有类型你可以创建以不同格式表达相同信息的新类型。创建映射类型需要两步用keyof取出既有类型的全部属性键再用索引签名[P in keyof T]遍历这些键并用索引访问类型T[P]取出每个属性的原始类型进行变换。原章节给出的核心示例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], };这里MyMappedTypeT遍历T的每个属性P把每个属性的类型T[P]包装成数组T[P][]于是MyNewType与MyType表达相同的信息但每个属性的形态都从标量变成了数组。映射类型修饰符就是在这段[P in keyof T]的声明处附加readonly、?等标记从而在复制结构的同时改变属性的可变性与可选性。二、三个核心修饰符总览本章节 mapped-type-modifiers.md 明确指出TypeScript 的映射类型修饰符共有三类能力修饰符语义作用readonly或readonly在映射结果中把属性标记为只读属性只能读取不能重新赋值-readonly在映射结果中把属性恢复为可变解除上层类型或映射带来的只读约束?在映射结果中把属性标记为可选属性可以缺省赋值的对象可以不提供该键注意前缀是显式写法readonly与readonly完全等价而-前缀是移除语义只能作用于映射类型内部用于把已有修饰符剥离掉。下面逐一展开。三、readonly/readonly批量加只读锁原章节给出的第一个示例type ReadOnlyT { readonly [P in keyof T]: T[P] }; // All properties marked as read-only这个泛型类型接收任意对象类型T遍历其全部属性并在每个属性前加上readonly。它的效果等价于把T的每个属性都用readonly重新声明一遍。回顾本书 readonly-properties.md 章节对只读属性的解释使用readonly修饰符可以阻止对属性的写入它确保属性不能被重新赋值但不提供完全不可变total immutability的保证。也就是说readonly只约束引用层面的赋值不阻止属性内部如嵌套对象、数组元素的修改。这是使用只读类型时最容易踩的坑readonly是浅层的。readonly是它的显式等价形式在需要强调添加语义与后面的-对应时使用type ReadOnlyExplicitT { readonly [P in keyof T]: T[P] }; // 与 readonly 写法完全等价下面演示实际使用效果type User { name: string; age: number; }; type ReadonlyUser ReadOnlyUser; // 等价于 { readonly name: string; readonly age: number; } const user: ReadonlyUser { name: Simon, age: 17 }; user.name John; // 编译错误无法为只读属性赋值值得注意的是readonly修饰符同样适用于索引签名。在 readonly-properties.md 中给出了如下声明形式type K { readonly [index: number]: string; };这说明只读修饰符不仅可以加在具名属性上也可以加在索引签名上从而锁定整个字典不可被改写。四、-readonly解除只读实现MutableT原章节的第二个示例展示了反向操作type MutableT { -readonly [P in keyof T]: T[P] }; // All properties marked as mutable-readonly表示移除只读修饰符它把输入类型中所有readonly属性全部还原为可变属性。这在处理来自外部库、as const断言或ReadonlyT包装过的类型时非常实用type Config { readonly apiUrl: string; readonly retries: number; }; type MutableConfig MutableConfig; // 等价于 { apiUrl: string; retries: number; } const config: MutableConfig { apiUrl: https://api.example.com, retries: 3 }; config.retries 5; // 合法MutableConfig 的属性已可写语法上readonly与-readonly是一对添加/移除操作?同样存在对应的-?移除形式详见第六节。注意-前缀必须紧跟在属性声明位置且-readonly只能出现在映射类型中不能用于interface或普通对象类型。五、?批量将属性变为可选原章节的第三个示例type MyPartialT { [P in keyof T]?: T[P] }; // All properties marked as optional?修饰符把T的每个属性都标记为可选。这与 optional-properties.md 章节中在属性名末尾加问号?即声明可选属性的规则一脉相承type X { a: number; b?: number; // Optional };映射版本MyPartialT则把每个属性都可选这件事自动化。它的典型使用场景包括配置合并用户只提供部分配置、表单部分更新PATCH 语义、测试替身等type Settings { theme: string; fontSize: number; showLineNumbers: boolean; }; type PartialSettings MyPartialSettings; // 等价于 { theme?: string; fontSize?: number; showLineNumbers?: boolean; } const patch: PartialSettings { fontSize: 14 }; // 只更新一个字段合法配合可选属性的默认值技巧同样出自 optional-properties.md可以在消费方优雅地处理缺省值const mergeSettings ({ theme dark, fontSize 16, showLineNumbers true }: PartialSettings) ({ theme, fontSize, showLineNumbers, });六、组合使用与-?同时操控两个维度三个修饰符之间可以自由组合。最典型的组合是既只读又可选这正是内置ReadonlyPartialT的效果type ReadonlyPartialT { readonly [P in keyof T]?: T[P] }; // 每个属性readonly 且 optional反过来映射类型也支持-?来移除可选性把可选属性恢复为必填type MyRequiredT { [P in keyof T]-?: T[P] }; // 移除 ?所有属性必填这一写法与-readonly对称-前缀统一表示剥离修饰符。RequiredT工具类型的标准实现正是基于-?的参见下文与 type-manipulation.md 中RequiredT的说明。同时移除只读与可选type ConcreteT { -readonly [P in keyof T]-?: T[P] }; // 所有属性可变且必填一个完整的读写权限变换实战示例type Draft { title?: string; body?: string; }; // 发布前要求所有字段齐全-?并且一旦发布不可再改readonly type Published { readonly [P in keyof Draft]-?: Draft[P] }; // 等价于 { readonly title: string; readonly body: string; } const post: Published { title: Hello, body: World }; post.title Hi; // 编译错误readonly七、与内置工具类型的关系ReadonlyT、PartialT、RequiredT的本质本书 predefined-conditional-types.md 与 type-manipulation.md 两个章节列出了大量内置工具类型其中与映射类型修饰符直接相关的有三个内置工具类型文档定义本质映射类型等价实现ReadonlyT将 T 的所有属性设为 readonly{ readonly [P in keyof T]: T[P] }PartialT将 T 的所有属性设为可选{ [P in keyof T]?: T[P] }RequiredT将 T 的所有属性设为必填{ [P in keyof T]-?: T[P] }其中Partial与Required互为逆操作Readonly与手写的MutableT互为逆操作。这正是本章节三个示例ReadOnly、Mutable、MyPartial的深层意义它们实际上是在重新发明语言内置的工具类型让你理解工具类型不是魔法而是映射类型 修饰符的语法糖。在 type-manipulation.md 中可以看到这些工具类型的具体使用示例type Person { name: string; age: number; }; type A PartialPerson; // { name?: string | undefined; age?: number | undefined; } type B Required{ name?: string; age?: number }; // { name: string; age: number; } type C ReadonlyPerson; const a: C { name: Simon, age: 17 }; a.name John; // Invalid注意一个细节PartialT在严格模式strictNullChecks 开启下会为每个可选属性附加| undefined。这源于可选属性在严格模式下意味着可能缺失也可能为 undefined的语言语义与本书 strictnullchecks.md 章节所讲的严格空值检查行为一致。八、深入原理同态映射、修饰符保留与浅层只读8.1 同态映射修饰符默认被保留当映射类型直接使用[P in keyof T]不附加任何修饰符时TypeScript 会原样保留输入类型T中已有的readonly与?修饰符。这种保形复制的映射被称为同态homomorphic映射。也就是说type CopyT { [P in keyof T]: T[P] }; type Source { readonly id: number; label?: string; }; // CopySource 仍是 { readonly id: number; label?: string; } // 只读与可选修饰符都被保留了下来如果希望复制结构的同时强制改变修饰符就必须显式使用/-前缀或直接书写修饰符如readonly、?。这正是-readonly、-?存在的意义——默认行为是保留主动移除才需要减号。8.2readonly的浅层性不是深度不可变再次强调 readonly-properties.md 中的警告readonly不提供任何完全不可变的保证。例如type ReadonlyUser { readonly profile: { nickname: string } }; const user: ReadonlyUser { profile: { nickname: simon } }; user.profile { nickname: other }; // 编译错误profile 是 readonly user.profile.nickname other; // 合法嵌套对象内部仍然可写如果需要深度只读必须递归地让每个嵌套属性都经过映射处理可结合条件类型实现递归映射这不是单个readonly修饰符能完成的。8.3 修饰符只能作用于映射类型内部readonly、-readonly、?、-?这些映射修饰符只能出现在映射类型{ [P in ...] ... }中。在普通对象类型、interface中书写-readonly或-?会直接报语法错误。interface中可用的只有声明形式的readonly与?见 optional-properties.md 与 readonly-properties.md。8.4 键重映射修饰符之外的第三种变换除了修饰符映射类型还支持用as子句重写键名这是映射类型三大变换能力改修饰符、改类型、改键名之一。在 exploring-the-type-system.md 的 Type Assertion 小节中给出了一个将修饰符、键重映射与模板字面量类型结合的示例type JType { [Property in keyof Type as prefix_${string Property}]: () Type[Property]; }; type X { a: string; b: number; }; type Y JX; // 等价于 { // prefix_a: () string; // prefix_b: () number; // }这里as \prefix_${string Property}把每个键重命名为带prefix_前缀的新键值类型则从属性类型变换为返回该类型的函数。虽然这不是修饰符的职责但理解as键重映射能让读者把映射类型这一整套能力keyof遍历 T[P]取类型 修饰符改可变性 as 改键名完整串起来。九、实战用一个综合案例串联全部修饰符下面把本章节三个示例改造成一个贴近真实项目的场景——数据模型的草稿可变可选→ 冻结只读必填生命周期// 1. 基础模型所有字段必填、可写 type Article { title: string; content: string; tags: string[]; }; // 2. 草稿态字段全部可选方便增量编辑等价于 PartialArticle type ArticleDraft { [P in keyof Article]?: Article[P] }; // 3. 发布态字段全部必填且只读组合使用 ? 移除与 readonly 添加 type PublishedArticle { readonly [P in keyof Article]-?: Article[P] }; // 4. 如果从外部如 API 响应或 as const 常量拿到的是只读类型 // 用 -readonly 解除锁便于在本地修改 declare const fetched: { readonly title: string; readonly content: string; readonly tags: string[] }; type MutableArticle { -readonly [P in keyof typeof fetched]: (typeof fetched)[P] }; // 5. 使用 const draft: ArticleDraft { title: Draft title }; // 合法其余字段可选 const published: PublishedArticle { title: T, content: C, tags: [ts] }; published.title T2; // 编译错误readonly这个案例中?、-?、readonly、-readonly四种映射修饰符全部登场分别对应放宽输入约束与收紧输出约束两种方向展示了映射类型修饰符在数据流边界表单、持久化、接口层上的典型用法。十、小结与延伸阅读映射类型修饰符是 TypeScript 类型系统的格式化工具readonly/readonly批量加只读锁-readonly批量解锁?批量放宽为可选-?批量收紧为必填默认的同态映射会保留既有修饰符显式的前缀操作符则用于强制改变它们。掌握这四个操作符你就掌握了ReadonlyT、PartialT、RequiredT等内置工具类型的实现原理能够在任何需要以类型为输入、以类型为输出的场景中写出精确的类型变换。本文对应的原文档位于 mapped-type-modifiers.md它在全书的目录结构中紧随 mapped-types.md见 table-of-contents.md 第 38、39 章。建议按以下顺序继续深入mapped-types.md映射类型的基础语法keyof[P in keyof T]T[P]readonly-properties.md 与 optional-properties.md只读与可选属性的声明语法type-manipulation.mdPartial、Required、Readonly等内置工具类型的完整清单与用法exploring-the-type-system.md包含as键重映射在内的更高级类型操作predefined-conditional-types.md条件类型视角下的工具类型速查表。赞分享文档教程【免费下载链接】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点击查看免费下载相关推荐BabelDOC突破性智能排版保留的PDF文档翻译解决方案BabelDOC突破性智能排版保留的PDF文档翻译解决方案 BabelDOC是一款专为学术和商业文档设计的智能翻译工具通过先进的排版分析和结构重建技术在保人工智能AI 应用NLP计算机视觉The Concise TypeScript Book 精讲函数返回类型推断Type from Func Return原理与实战The Concise TypeScript Book 精讲函数返回类型推断Type from Func Return原理与实战 本篇技术指南以开源仓库文档教程poly_data进阶技巧使用process_live实现实时数据流处理poly_data进阶技巧使用process_live实现实时数据流处理 poly_data是一个强大的Polymarket数据检索工具能够高效获取、处理和文档教程上一篇unified国际化支持多语言内容处理完整方案下一篇前端开发者必备You-Dont-Need-JavaScript项目完全使用手册创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考