重构 TypeScript 遗留代码:从 A 到 Z
处理遗留代码是许多开发者无法回避的现实。这类项目通常是用纯 JavaScript 编写的,没有类型,依赖关系混乱,并且缺乏测试。TypeScript 提供了一套强大的工具,可以逐步改进此类代码,使其更安全、更易读、更易维护。在本文中,我们将从分析旧代码到创建清晰的类型化架构,逐步展开。
1. 准备与策略:从何入手?
在修改代码之前,你需要创建一个“安全网”。重构的主要规则是:不要破坏正在运行的功能。从搭建环境开始。
1.1. 初始化 TypeScript 与严格模式
安装 TypeScript 并创建配置文件 tsconfig.json。初始阶段可以禁用严格检查,以免被大量错误淹没,但之后需要逐步启用这些检查。
{ "compilerOptions": { "target": "ES2020", "module": "commonjs", "strict": true, // 从 false 开始,然后逐渐启用 "noImplicitAny": true, "strictNullChecks": true, "outDir": "./dist", "rootDir": "./src", "allowJs": true, // 允许混用 JS 和 TS "checkJs": false // 暂时不检查 JS 文件 }, "include": ["src/**/*"]}1.2. 自动化与测试
添加代码检查工具(ESLint)和格式化工具(Prettier)。如果没有测试,至少为关键部分编写集成测试。这是你防止回归的保障。
- ESLint 配合 TypeScript 插件可以捕获逻辑错误。
- Jest 或 Vitest 用于单元测试。
- 设置 pre-commit hooks(Husky),确保代码在提交前经过检查。
2. 逐步迁移:从 JS 到 TS
不要试图一次性重写所有内容。使用迭代的方式。这里最好的工具是 tsconfig 中的 allowJs: true。这允许你从 TypeScript 中引用 JavaScript 文件。
2.1. 重命名文件与基础类型注解
从重命名最简单的文件开始,比如将 utils.js 或 helpers.js 重命名为 .ts。编译器会开始报告隐式 any 的错误。你的任务是描述函数的签名。
之前 (JavaScript):
function calculateDiscount(price, percent) { return price - (price * percent / 100);}之后 (TypeScript):
function calculateDiscount(price: number, percent: number): number { return price - (price * percent / 100);}2.2. 处理 "any" 和 "unknown"
遗留代码中存在大量隐式 any。不要试图立刻完美地注解所有类型。使用 any 作为临时解决方案,但要用 TODO 标记。对于来自外部来源(API、localStorage)的数据,最好使用 unknown 并随后进行类型检查。
// 临时解决方案function processData(data: any) { // TODO: 添加合适的类型 console.log(data.name);}
// 处理外部数据的正确方法function processUserData(data: unknown) { if (typeof data === 'object' && data !== null && 'id' in data) { const user = data as { id: number; name: string }; console.log(user.name); }}3. 重构复杂结构
完成基础类型注解后,接下来要改进架构。遗留代码通常存在“上帝对象”和混乱的调用链问题。
3.1. 提取接口与类型
不要传递结构未知的对象,而是创建接口。这能让代码实现自文档化。
// 不好的做法function saveUser(user: any) { // 不清楚 user 有哪些字段}
// 好的做法