☰
TypeScript Mapped Types 完全指南:用 keyof 与映射语法从既有类型派生新类型(typescript-book 实战解读)
2026/9/25 5:48:00 网站建设 项目流程
  • 文档
  • 教程

【免费下载链接】typescript-book

The 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-types 章节 为骨架,系统讲解 TypeScript Mapped Types(映射类型)的完整语法体系:从keyof属性遍历、T[P]索引访问、readonly/?修饰符,到键重映射(as子句)与条件类型、模板联合类型的组合运用。读完本文,你将掌握如何用映射类型把"信息相同、形态不同"的类型变换逻辑固化进类型系统,并能自行实现Readonly、Partial这类内置工具类型的底层原理。

一、什么是 Mapped Type:以类型变换类型

Mapped Type 是 TypeScript 中一类基于"既有类型"创建"新类型"的语法结构。它的核心思想是:不手工重写每个属性,而是通过一个映射函数遍历既有类型的每一个属性,逐一变换后生成新类型。映射既有类型,就能创建出"表达相同信息、但采用不同格式"的全新类型。

要创建一个映射类型,需要两步:

  1. 使用keyof操作符取出既有类型的所有属性键;
  2. 对每个键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]与修饰符加减法后,你可以亲手还原出这些标准工具类型,也就能理解它们在底层是如何工作的,进而写出自己的定制版本。

八、典型实战场景小结

结合本章节与仓库上下文,映射类型在真实项目中的高价值场景包括:

  1. 批量不可变化:对接口返回的 DTO 统一加readonly,防止意外改写;
  2. 表单/配置二态化:用Partial<T>表示"部分字段已填",用Required<T>表示"全部必填";
  3. 键名工程化:用as+ 模板字面量生成get_${key}风格的访问器类型或id_${key}风格的事件类型;
  4. 按条件过滤字段:结合条件类型只保留字符串字段、函数字段等特定类型的键;
  5. 状态机约束:从字段联合生成"每个字段一组状态"的完整类型空间,与 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.

项目地址:https://gitcode.com/gh_mirrors/typ/typescript-book
点击查看免费下载
上一篇:PiKVM Redfish传感器阈值配置完整指南:精准设置告警触发条件
下一篇:微信AI助手完整搭建指南:用 wechat-bot 快速接入多种大模型自动回复

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询