海缆系统重写手记:从 Vanilla JS 到 React + TypeScript
距离上次更新已经过了一个多月。这段时间发生了一件大事——整个系统推倒重写了。 上篇文章结尾我说过:“项目规模已经到了 Vanilla JS 的舒适边界”。当时觉得还能再撑一阵,结果只过了两周,代码就彻底失控了。 重写的导火索 v1.13 之后又迭代了几个版本(v1.14 到 v1.16),每次都在原有架构上加功能: 合同终止与续约:销售订单和库存资源都需要支持提前终止、到期续约、逐项操作 批次容量管理:Base + Batch 模式的成本分摊 CSV/Excel 导入:三步向导式批量数据导入 移动端卡片视图:客户、供应商列表的响应式适配 每加一个功能,都要在多个 IIFE 模块间穿针引线。salesForm.js 拆了又拆、拆出了十几个子模块,inventory.js 也开始走上同样的路。代码量突破了 21000 行,但真正让我下决心的不是行数,而是两个具体的痛点: 1. 状态管理的噩梦 续约弹窗需要同时操作多个成本项,每个成本项有独立的日期、金额、选中状态。用 DOM 操作来维护这些状态,代码写出来是这样的: // 每次点击复选框,手动同步所有关联 DOM const checkbox = card.querySelector('.renew-checkbox'); const dateInput = card.querySelector('.renew-start-date'); const termInput = card.querySelector('.renew-term'); if (checkbox.checked) { dateInput.disabled = false; termInput.disabled = false; recalcEndDate(card); updateSubmitButton(); } else { dateInput.disabled = true; // ... 还要清理 N 个联动状态 } 同样的逻辑用 React 写: const [items, setItems] = useState(initialItems); // 选中状态变了,UI 自动更新 <Checkbox checked={item.selected} onCheckedChange={(v) => updateItem(i, { selected: v })} /> 2. 测试的死角 v1.x 加了测试框架,但能测的只有纯函数(日期计算、状态判断)。涉及 DOM 的业务逻辑根本没法测,因为每个模块都依赖全局的 App 对象和一堆 DOM 节点。 ...