首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01 | 重塑“类型思维”
02 | 类型基础(1):强类型与弱类型
03 | 类型基础(2):动态类型与静态类型
04 | 编写你的第一个TypeScript程序
05 | 基本类型
06 | 枚举类型
07 | 接口(1):对象类型接口
08 | 接口(2):函数类型接口
09 | 函数相关知识点梳理
10 | 类(1):继承和成员修饰符
11 | 类(2):抽象类与多态
12 | 类与接口的关系
13 | 泛型(1):泛型函数与泛型接口
14 | 泛型(2):泛型类与泛型约束
15 | 类型检查机制(1):类型推断
16 | 类型检查机制(2):类型兼容性
17 | 类型检查机制(3):类型保护
18 | 高级类型(1):交叉类型与联合类型
19 | 高级类型(2):索引类型
20 | 高级类型(3):映射类型
21 | 高级类型(4):条件类型
22 | ES6与CommonJS的模块系统
23 | 使用命名空间
24 | 理解声明合并
25 | 如何编写声明文件
26 | 配置tsconfig.json(1):文件选项
27 | 配置tsconfig.json(2):编译选项
28 | 配置tsconfig.json(3):工程引用
29 | 编译工具:从ts-loader到Babel
30 | 代码检查工具:从TSLint到ESLint
31 | 使用Jest进行单元测试
32 | 创建项目
33 | 组件与类型(1):函数组件与类组件
34 | 组件与类型(2):高阶组件与Hooks
35 | 事件处理与数据请求
36 | 列表渲染与路由
37 | Redux与类型
38 | 搭建服务端开发环境
39 | 列表的CRUD
40 | 导出Excel
41 | 搭建Vue开发环境
42 | 组件封装
43 | 组件发布
44 | 共存策略
45 | 宽松策略
46 | 严格策略
当前位置:
首页>>
技术小册>>
TypeScript开发实战
小册名称:TypeScript开发实战
### 第20章 高级类型(3):映射类型 在TypeScript的世界里,高级类型不仅仅是语言的点缀,它们是构建复杂、灵活且安全类型系统的基石。本章将深入探讨TypeScript中的一个极为强大的特性——映射类型(Mapped Types),这一特性允许我们根据已存在的类型自动地创建新类型,极大地提高了类型定义的复用性和表达能力。 #### 20.1 映射类型基础 映射类型是一种通过旧类型(我们称之为“源类型”)创建新类型的方式,新类型的每个属性都基于旧类型中相应属性的某种变换规则来定义。这种变换规则可以是修改属性的类型、添加或删除属性等。映射类型的基本语法如下: ```typescript type MappedType<T> = { [P in keyof T]: /* 转换逻辑 */ }; ``` 在这里,`keyof T` 获取了类型 `T` 的所有键的联合类型,而 `[P in keyof T]: ...` 则是对这些键进行遍历,并对每个键 `P` 应用转换逻辑来定义新类型的相应属性。 #### 20.2 示例:只读与可选属性 ##### 20.2.1 只读映射类型 假设我们有一个基础类型,我们希望基于这个类型创建一个所有属性都是只读的版本。这可以通过映射类型轻松实现: ```typescript type Readonly<T> = { readonly [P in keyof T]: T[P]; }; interface Todo { title: string; completed: boolean; } const todo: Readonly<Todo> = { title: "Learn TypeScript", completed: false, }; // 错误:不能给 readonly 属性赋值 todo.title = "Another Task"; // TypeScript 会报错 ``` ##### 20.2.2 可选属性映射类型 类似地,如果我们想要一个所有属性都是可选的类型,也可以这样做: ```typescript type Partial<T> = { [P in keyof T]?: T[P]; }; const partialTodo: Partial<Todo> = { completed: true, // title 是可选的,可以省略 }; ``` #### 20.3 深入转换逻辑 映射类型的真正威力在于其转换逻辑的灵活性。我们可以对属性的类型进行复杂的变换,比如添加前缀、后缀,或者基于条件改变类型等。 ##### 20.3.1 类型变换示例 假设我们有一个用户信息类型,我们希望创建一个新类型,其所有属性类型都变为字符串: ```typescript type Stringified<T> = { [P in keyof T]: string; }; interface User { name: string; age: number; isAdmin: boolean; } type StringifiedUser = Stringified<User>; // 相当于: // type StringifiedUser = { // name: string; // age: string; // isAdmin: string; // }; ``` ##### 20.3.2 条件类型在映射类型中的应用 条件类型(Conditional Types)与映射类型结合使用,可以实现更复杂的类型变换逻辑。例如,我们可能想要创建一个类型,其中只包含源类型中的数字类型属性: ```typescript type NumbersOnly<T> = { [P in keyof T as T[P] extends number ? P : never]: T[P]; }; interface Mixed { id: number; name: string; age: number; isActive: boolean; } type NumbersFromMixed = NumbersOnly<Mixed>; // 相当于: // type NumbersFromMixed = { // id: number; // age: number; // }; // 注意:isActive 和 name 不包含在内,因为它们不是数字类型 ``` 在这个例子中,`as T[P] extends number ? P : never` 是一个条件表达式,用于筛选键。如果属性类型是 `number`,则保留该键;否则,使用 `never` 排除它。 #### 20.4 映射类型的高级用法 ##### 20.4.1 递归映射类型 有时,我们需要对嵌套的对象结构进行类型变换,这就需要用到递归映射类型。递归映射类型通常与泛型约束和条件类型结合使用,以处理复杂的嵌套结构。 ```typescript type DeepReadonly<T> = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P]; }; interface NestedObject { id: number; details: { name: string; attributes: { size: number; }; }; } const nested: DeepReadonly<NestedObject> = { id: 1, details: { name: "Example", attributes: { size: 100, }, }, }; // 错误:不能修改深层嵌套属性 nested.details.attributes.size = 200; // TypeScript 会报错 ``` ##### 20.4.2 映射类型的键重命名 虽然TypeScript没有直接提供键重命名的映射类型语法,但我们可以通过结合索引签名和条件类型来模拟这一行为。这种方法相对复杂,通常涉及到映射类型内部的额外逻辑来捕获旧键并映射到新键。 #### 20.5 实战应用 映射类型在实际项目中有着广泛的应用。例如,在构建REST API客户端库时,我们可能需要根据API响应自动生成TypeScript类型。通过映射类型,我们可以轻松地根据API的JSON结构生成TypeScript接口,从而简化类型定义工作,减少出错的可能性。 另外,在构建复杂的状态管理库(如Redux、Vuex)时,映射类型也非常有用。我们可以利用映射类型来自动推导状态树中每个部分的类型,或者为状态树的各个部分添加额外的属性(如加载状态、错误信息)而不必手动编写大量重复的代码。 #### 20.6 小结 映射类型是TypeScript中一个非常强大的特性,它允许我们根据已存在的类型自动地创建新类型,极大地提高了类型定义的复用性和表达能力。通过掌握映射类型的基本语法和高级用法,我们可以编写出更加灵活、安全且易于维护的TypeScript代码。无论是在构建大型应用程序还是开发库和框架时,映射类型都是不可或缺的工具之一。
上一篇:
19 | 高级类型(2):索引类型
下一篇:
21 | 高级类型(4):条件类型
该分类下的相关小册推荐:
TypeScript入门指南
TypeScript 全面进阶指南
剑指TypeScript