第四章:打造随需应变的“通用模具” - 泛型
第四章:打造随需应变的“通用模具” - 泛型
Prorise第四章:打造随需应变的“通用模具” - 泛型
摘要: 欢迎来到第四章。在本章,我们将完成一次从“具体实现”到“抽象建模”的思维升级。我们将直面当前 Todo 应用中因处理不同数据类型而导致的逻辑重复问题,并引入 TypeScript 的核心利器——泛型——来解决它。您将不再编写一次性的函数,而是学会创建如同“通用模具”般的、可适应多种数据类型的、高度复用且类型安全的函数与接口。我们将立即在 Todo 应用中实践,重构出一个通用的 API 数据结构,让您切身感受泛型在真实工程中的威力。
4.1. 痛点呈现:重复的逻辑与失控的 any
随着应用变得复杂,我们不可避免地需要处理多种不同类型的数据。让我们在 Todo 应用中模拟这个场景,并看看它如何立刻导致代码冗余。
4.1.1. 场景:为应用添加“用户”数据
假设我们的 Todo 应用现在需要显示当前的用户信息。首先,我们需要在项目中定义用户的“契约”。
第一步:创建 types.ts
并定义 User
接口
为了更好地组织代码,我们创建一个专门存放类型定义的文件。
文件路径: src/types.ts
1 | // 我们之前定义的 Todo 和 Filter 类型也移到这里 |
第二步:更新 main.ts
以使用新类型
文件路径: src/main.ts
1 | import './style.css'; |
4.1.2. 痛点:重复的查找逻辑
现在,我们需要一个函数根据 ID 查找 Todo,也需要一个几乎完全一样的函数来根据 ID 查找用户。
文件路径: src/main.ts
(在 state 定义下方添加)
1 | function findTodoById(id: number): Todo | undefined { |
问题显而易见:这两个函数的 逻辑完全相同,唯一的区别就是处理的数据类型(Todo
和 User
)不同。在工程中,这种重复是“万恶之源”,它增加了维护成本,违反了 DRY (Don’t Repeat Yourself) 原则。
4.1.3. 失败的抽象:使用 any
导致的类型安全丧失
一个自然的冲动是使用 any
来创建一个通用函数。让我们看看为什么这是一个陷阱。
1 | function findItemById_any(items: any[], id: number): any { |
any
以牺牲类型安全为代价换取了灵活性,这对于追求健壮性的工程师来说是不可接受的。它让我们回到了序章中的“混沌”状态。
4.2. 泛型 <T>
的引入与泛型约束
现在,让我们引入真正的“利器”——泛型。泛型允许我们编写一个函数,并为其中的类型创建一个“占位符”(通常表示为 <T>
),在使用该函数时再填入具体的类型。
4.2.1. 重构:创建一个通用的 findItemById<T>
函数
文件路径: src/utils.ts
(我们创建一个新的工具文件)
1 | // T 是一个类型变量,一个“占位符”。 |
痛点分析: TypeScript 报错是因为,它只知道 T
是“某种类型”,但它不能保证“某种类型”一定拥有一个 id
属性。我们需要给泛型添加约束。
4.2.2. 泛型约束 (extends
):让泛型更“聪明”
解决方案: 我们使用 extends
关键字来告诉 TypeScript,传入的类型 T
必须满足 某个“形状”。
文件路径: src/utils.ts
(修改 findItemById
函数)
1 | // 我们约束 T 必须是这样一个对象:它至少要有一个 number 类型的 id 属性。 |
第三步:在 main.ts
中使用我们的通用工具
文件路径: src/main.ts
1 | import './style.css'; |
我们成功地用一个 完全类型安全 的泛型函数替换了两个重复的函数。这就是泛型在工程化中的核心价值:在不牺牲类型安全的前提下,实现最高程度的代码复用。
4.3. 泛型接口:为 API 响应建立通用契约
痛点: 我们的应用将来会从 API 获取各种数据:Todo 列表、用户列表、配置项等。这些 API 响应通常有统一的结构,例如:
1 | { status: 200, message: "OK", data: [...] } |
为每一种数据都写一个 TodoApiResponse
, UserApiResponse
接口,又会陷入重复的泥潭。
解决方案: 使用泛型接口,创建一个可复用的 ApiResponse<T>
模板。
第四步:在 types.ts
中定义泛型接口
文件路径: src/types.ts
1 | // ... 其他类型定义 ... |
第五步:在 api.ts
中应用泛型接口
文件路径: src/api.ts
(新建文件)
1 | import { Todo, User, ApiResponse } from './types'; |
现在,我们有了一个统一的、可复用的 ApiResponse<T>
接口来描述所有来自后端的数据结构,极大地提升了代码的一致性和可维护性。
4.4 本章小结
类型工具 | 核心价值 | 在我们 Todo 应用中的实践 |
---|---|---|
泛型函数 <T> | 消除代码重复,同时保持类型安全。 | 创建了一个通用的 findItemById 工具函数,可用于查找任何带 id 的对象。 |
泛型约束 extends | 为泛型添加规则,使其更智能、更安全。 | 约束了 findItemById 的参数类型必须包含 id 属性。 |
泛型接口 | 创建可复用的数据结构“模板”。 | 定义了标准的 ApiResponse<T> 接口,统一了所有 API 响应的形状。 |
在本章,我们引入了泛型这一强大的抽象工具,将 Todo 应用的可复用性提升到了一个新的层次。我们学会了不再为每一个具体类型编写重复逻辑,而是去思考和构建通用的解决方案。在下一章,我们将面对一个新的挑战:随着应用逻辑增多,main.ts
文件将变得越来越臃肿。