14 | 老页面怎么改不坏?定位 + 增量 + 回归
前言:上一篇聊了新页面怎么从零建。但真实项目里,更多需求落在已有页面上------加个标签、改个状态映射、跳转多带个参数。这栋楼的数据流、静态资源、代码风格都定了,骨架的核心从「怎么建」变成「在哪改、怎么改不坏老的」。下面的范式,是我在老系统上反复踩坑后沉淀出的结论。
一、接上篇:从「新页面骨架」到「老页面改造」
上一篇的四种骨架是创建式------从契约到 UI 到糅合,逐层从零垒。但老页面改造完全反过来:
text
新页面骨架:创建式 ------ 契约层 → 文件层 → 数据层 → UI层 → 糅合 → 联调/验收
已有页面改造:定位式 + 增量式 + 回归式 ------ 页面已存在,核心是「在哪改、怎么改不坏老的」
老页面的数据流、静态资源、代码风格都已定------老范式还是新范式都摆在那,不用你发明。骨架核心变成两问:
- 在哪改?------定位。改动落在哪个文件、哪个函数、哪个区块。
- 怎么改不坏老的?------增量 + 回归。只做增量,不顺手重构;老功能关键路径必过一遍。
两条铁律(老系统改造 90% 的失败死于此):
- 最小改动:只做增量,不顺手重构老代码、不改代码风格、不动无关区块。
- 每次回归:新功能验收之外,老功能关键路径必过一遍。
和上一篇的关键差异 :新页面骨架从「页面」从零垒起;老页面改造面对的是「改造单元 」------一个业务模块下通常不止一个页面,最常见的就是列表页 + 详情页 。改造可能只落在其中一个页面,也可能横跨多个页面。所以骨架要做成通用母版:以改造单元为根、每页一套骨架(场景 A 全六层 / 场景 B 薄骨架),套任何改造场景都能用。
二、核心观点:一套母版,按场景分叉
无论改列表页、详情页还是改整个模块,骨架是一致的------通用母版:改造单元为根、每页一套骨架(场景 A 全六层 / 场景 B 薄骨架):
text
📋 <改造单元> · 老页面改造骨架(定位 + 增量 + 回归)
├── <页面1>(如列表页)
│ ├── 0️⃣ 未提供产物声明 → 接口、枚举、figma/原型图
│ ├── 1️⃣ 实施流程 → 契约 → 文件 → 数据 → UI → 回归
│ ├── 2️⃣ 契约层 → 枚举、接口、公用方法
│ ├── 3️⃣ 文件层 → 定位式改动(改 / 增 / 不动)
│ ├── 4️⃣ 数据层 → 按代码风格分叉(老范式 / 新范式)
│ ├── 5️⃣ UI 层 → 场景分叉(场景A 视图diff / 场景B 不动视图 + 场景豁免)
│ └── 6️⃣ 验收 → 新功能验收 + 回归
└── <页面2>(如详情页)
└── 每页一套骨架,层结构与上一致
| 页面骨架层 | 已有页面场景 | 做什么 |
|---|---|---|
| 0️⃣ 未提供产物声明(接口 / 枚举 / figma/原型图) | 锚点 + 增量,缺口在产物声明 | 缺哪样 → 走对应兜底方案 |
| 1️⃣ 实施流程 | 依次走各层 | 契约 → 文件 → 数据 → UI → 回归 |
| 2️⃣ 契约层(枚举 / 接口 / 公用方法) | 复用为主 + 新增 + 兼容 | 查冲突 / 调用方兼容 |
| 3️⃣ 文件层(改 / 增 / 不动) | 定位式改动清单 | 不建楼,只动区块 |
| 4️⃣ 数据层(老范式 / 新范式) | 按既有代码风格 | 与既有风格一致 |
| 5️⃣ UI 层(场景A / 场景B + 豁免) | 视图diff / 不动视图 | 先判场景再动 |
| 6️⃣ 验收(验收 + 回归) | 新功能验收 + 回归 | 老功能关键路径必过 |
回归是已有页面改造独有的必选项------新页面没有「老功能」,改造有。
同一张母版,改动形态不同就分叉成三种变体 :区分场景 A / B 的标准只有一个------这次动不动视图;是否走第三种「多页面小改动」,另看改动大小与交付批次:
| 变体 | 改什么 | 骨架 | 重点 |
|---|---|---|---|
| 场景 A | 视图 + 逻辑 | 全 6 层 | 视图 diff + 不动区块回归 |
| 场景 B | 纯逻辑,不改视图 | 薄骨架(豁免 5️⃣ UI 层) | 老功能关键路径不坏 |
| 多页面小改动 | 每页 ≤3 点、同批交付 | 单 change | 太碎合,逐页一套骨架 |
三、变体一 · 场景 A:动视图(视图 diff,全 6 层)
一句话判定:这次要动视图------加标签 / 改布局 / 换展示。figma 没出也能开工,有原型截图就能 diff。
先一句话分清 A / B:这次改不改变页面长啥样?
| 场景 A · 动视图 | 场景 B · 不动视图 | |
|---|---|---|
| 典型改动 | 加标签 / 改布局 / 换展示 | 改口径 / 跳转带参 / 换数据源 |
| 怎么动 | 拿原型图/Figma对现状,改差异那几块 | 只改背后逻辑,页面不动 |
| UI 层 | 要做 UI 对比 | 直接跳过(豁免) |
| 骨架 | 全 6 层 | 薄骨架(豁免 5️⃣ UI 层) |
| 关注 | 差异改对 + 不动的区域回归 | 老功能关键路径不坏 |
关键机制(两个)
① 视图 diff:拿原型图/Figma对着现状代码,把页面分成三类,只动该动的
改老页面,别一上来就动手。先把原型截图(figma)和现在的代码摆在一起,逐块对比,每一块只可能是三种情况之一:
| 属于哪种 | 怎么看 | 怎么办 |
|---|---|---|
| 新增 | 原型(figma)里有、代码里没有 | 新建子组件来展示 |
| 修改 | 原型(figma)里有、和代码长得不一样 | 选改法(看第②条) |
| 不动 | 原型(figma)里没变化 | 千万别碰,记进清单 |
② 实现路线:差异小就在原处改,差异大就新建替换
原型是最终标准,别被老代码的写法绑住:
- 原处改:差异小、改动点不多 → 直接在现有 JSX + 样式里改,最省事
- 新建替换:差异大 / 要重新排 → 新建一个组件,把原区块整个换掉(原代码删掉),别在老代码里硬塞,越塞越乱
- 边界:无论哪种改法,只动目标区块,不牵连相邻无关区块------替换 ≠ 顺手重构
案例:订单列表页 · 新增状态标签(场景A)
接到「列表页卡片要加状态标签」,figma 还没出,只有一张原型截图。套母版逐层填:
sql
📋 订单列表页(老页面改造 · 场景A · 新增状态标签)
├── 0️⃣ 未提供产物
│ ├── figma:未出 → 以原型截图 diff 为基准,figma 到位后精调
│ ├── 接口:缺返回样例 → mock 短路兜底
│ └── 枚举:状态映射缺 → explore 补
├── 1️⃣ 实施流程: 2️⃣ 契约 → 3️⃣ 文件 → 4️⃣ 数据 → 5️⃣ UI diff → 6️⃣ 验收+回归
├── 2️⃣ 契约层
│ ├── 枚举:复用已有 orderStatus(查命名冲突),新增 orderStatusLabel 映射
│ ├── 接口:复用已有列表接口加字段(查调用方兼容 / 存量数据兜底 / 灰度)
│ └── 公用方法:状态映射方法若跨页面复用,改前查调用方
├── 3️⃣ 文件层
│ ├── 改:index.jsx 卡片状态区渲染(+ 新 SCSS 块)
│ ├── 增:view/StatusTag/(子组件 + index.scss)
│ └── 不动:分页区 / 空态 / 头部筛选
├── 4️⃣ 数据层
│ ├── 老范式:store.js 加 orderStatusLabel 字段 + action.js 加查询方法
│ └── 新范式:useStore.js 加字段/方法(按页面既有风格二选一)
├── 5️⃣ UI 层(场景A 视图diff,原型截图基准)
│ ├── 新增区块:状态标签(view/StatusTag + 新 SCSS)
│ ├── 修改区块:卡片状态区(差异小 → 第②条·原处改,标「修改」)
│ └── 不动区块:分页 / 空态 / 筛选
└── 6️⃣ 验收/边界
├── 功能:标签映射正确(GIVEN/WHEN/THEN)
├── figma 精调:figma 到位后验证视觉边界(标签长文案 / 不同状态 / 像素对齐)
├── mock 替换:IS_MOCK 置 false,删 mockData.js 及短路分支
├── 回归:老功能关键路径(列表加载 / 分页 / 跳详情 / 空态 / 接口失败)
└── 边界:空数据 / 接口失败 / 存量旧数据兼容
四、变体二 · 场景 B:不动视图(纯逻辑,薄骨架 + 豁免)
一句话判定:这次不动视图,只改逻辑------改金额口径 / 跳转带参 / 换数据源。
关键机制(两个)
- 薄骨架。视图没动,六层不用全走------保留 0️⃣ 锚点 + 2️⃣ 契约(复用为主)+ 3️⃣ 文件层 + 4️⃣ 数据层(按需)+ 6️⃣ 回归,只豁免 5️⃣ UI 层,1️⃣ 实施流程压缩成薄流程:
text
0️⃣ 锚点+增量 → 2️⃣ 契约(复用)→ 3️⃣ 文件层定位 → 4️⃣ 数据层(按需)→ 6️⃣ 回归验证(老功能不坏)
- 场景豁免。视图没动,做区块 diff 没意义------5️⃣ UI 层直接跳过,只走文件层 + 数据层 + 回归。
案例:订单详情页 · 金额口径调整(场景B)
同一个模块,详情页是另一类改动------纯逻辑、不动视图。接到「详情页展示金额改为优惠后口径」,视图完全不变,只改数据计算:
less
📋 订单详情页(老页面改造 · 场景B · 金额口径调整)
├── 0️⃣ 未提供产物
│ ├── figma:已出,且本次视图不变
│ ├── 接口:已提供(复用已有详情接口)
│ └── 枚举:已提供(复用已有枚举)
├── 1️⃣ 实施流程:薄骨架(场景B 豁免 5️⃣ UI 层,不做视图 diff)------3️⃣ 文件层 → 4️⃣ 数据层 → 6️⃣ 回归
├── 2️⃣ 契约层
│ ├── 枚举:复用(无新增)
│ ├── 接口:复用(无新增字段)
│ └── 公用方法:新增/调整金额计算公用方法,改前查调用方(列表页可能也在用,是回归对象)
├── 3️⃣ 文件层
│ └── 改:pureBusiness 金额计算函数(计算逻辑本体)+ action 引用(视图不动)
├── 4️⃣ 数据层
│ ├── 老范式:action.js 只更新引用/调用,计算逻辑统一在 pureBusiness
│ └── 新范式:useStore.js 只更新引用/调用(按页面既有风格二选一)
├── 5️⃣ UI 层(场景B 豁免)
│ └── 视图不变 → 豁免,不做视图 diff,不做区块 diff
└── 6️⃣ 验收/边界
├── 功能:金额口径正确(GIVEN/WHEN/THEN)
├── 回归:老跳转路径不坏(详情加载 / 数据展示 / 列表返回)+ 公用方法其他调用方不坏
└── 边界:老 UI 不受影响 / 存量旧数据兼容
同模块两页两景对比 :列表页的标签改动走全 6 层(场景 A),详情页的口径改动走薄骨架------豁免 5️⃣ UI 层,只走文件层 + 数据层 + 回归(场景 B),薄得多------判断标准只有一个:这次动不动视图。
五、变体三 · 多页面小改动(合一个 change)
适用场景
改造横跨多个页面,但每页改动都很小 (≤3 点)、同批交付 、共用契约。
关键机制(两个)
- 太碎合 (定 change 粒度)。多页面小改动每页都够不上一个独立 change,强行拆分反而把同批交付、共用契约的改动拆散了------组合成一个 change,不要拆成多个。
- 每页一套骨架 (定页内组织)。change 内部不写「单份总括」,而是沿用通用骨架逐页展开:模块根(0️⃣ 产物声明 + 1️⃣ 实施流程 + 跨页共用契约)+ 每页一套骨架(场景 A 全六层 / 场景 B 薄骨架)。场景 B 豁免 5️⃣ UI 层,2️⃣ 契约层、4️⃣ 数据层仅在有相应改动时保留,否则省略------纯逻辑小改动不展开冗余层。
边界:适用每页小(≤3 点)、页数少(≤3-4 页)、同批交付、共用契约。某页变大或有独立验收、页数膨胀 → 拆回独立 change,否则「太碎合」变「过聚合」。
案例:订单模块 · 三页小改动
同一批需求里,列表页加标签(场景A)、来源页跳转带参(场景B)、活动页接灰度(场景B)------三个页面都是小改动、同批交付、共用契约,组合成一个 change。模块根声明产物与共用契约,三页逐页展开:
less
📋 订单模块(老页面改造 · 多页面小改动 · 单 change)
├── 0️⃣ 未提供产物(模块级总括)
│ ├── figma:未出 → 按原型截图 diff,figma 到位后精调
│ ├── 接口:缺返回样例 → mock 短路兜底
│ └── 枚举:缺 → explore 补
├── 1️⃣ 实施流程:逐页走各层------每页一套骨架(场景A 全六层 / 场景B 薄骨架),共用契约先提模块级
├── 共用契约
│ └── 公用方法:orderStatusLabel 状态映射(列表/详情共用)→ 改前查调用方,是所有页面回归对象
├── 页面1 · 列表页(场景A · 全六层)
│ ├── 2️⃣ 契约层:复用 orderStatus,新增 orderStatusLabel;接口复用加字段
│ ├── 3️⃣ 文件层:改 卡片状态区(+新 SCSS);增 view/StatusTag/;不动 分页/空态/筛选
│ ├── 4️⃣ 数据层:useStore.js 加 orderStatusLabel 字段/方法(按既有风格)
│ ├── 5️⃣ UI 层:新增区块 标签;修改区块 卡片状态区(第②条·原处改);不动区块 分页/空态/筛选
│ └── 6️⃣ 验收/回归:标签映射正确(GIVEN/WHEN/THEN);回归 列表加载/分页/跳详情/空态
├── 页面2 · 来源页(场景B · 薄骨架,无新增契约/数据层改动 → 省略 2️⃣/4️⃣)
│ ├── 3️⃣ 文件层:改 行点击回调 + 路由传参(带 orderId)
│ └── 6️⃣ 验收/回归:跳转带参正确(GIVEN/WHEN/THEN);回归 老跳转路径不坏(详情收参/列表返回)
└── 页面3 · 活动页(场景B · 薄骨架,无新增契约/数据层改动 → 省略 2️⃣/4️⃣)
├── 3️⃣ 文件层:改 初始化后接灰度开关
└── 6️⃣ 验收/回归:灰度开关生效(GIVEN/WHEN/THEN);回归 活动页老展示不坏
六、6️⃣ 验收:验收 + 回归
最后一层------新功能验收 + 回归 。回归是老系统改造独有的必选项:新页面没有「老功能」,改造有。
text
## 验收
- [ ] 新功能验收:GIVEN/WHEN/THEN(每功能怎么算对)
- [ ] figma 边界验证:figma 到位后验证视觉边界(figma 未覆盖状态 / 边界样式 / 像素对齐 / 不同状态表现)
- [ ] mock 替换:接口联调后 IS_MOCK 置 false,删 mockData.js 及短路分支,校验真实返参
- [ ] 回归:老功能关键路径各过一遍(列表 / 详情 / 操作 / 跳转 / 轮询)
## 边界
- 空数据 / 接口失败 / 存量旧数据兼容 / figma 未覆盖的视觉边界 / 场景 B 改动是否影响老 UI
两条铁律在这里落地:最小改动 的产物就是「不动」清单;每次回归的产物就是这层的老功能关键路径清单。figma 边界验证与 mock 替换,只在存在对应未提供产物时触发。
七、怎么选:场景对照表
| 变体 | 改什么 | 骨架 | 重点 |
|---|---|---|---|
| 场景 A | 视图 + 逻辑 | 全 6 层 | 视图 diff + 不动区块回归 |
| 场景 B | 纯逻辑,不改视图 | 薄骨架(豁免 5️⃣ UI 层) | 老功能关键路径不坏 |
| 多页面小改动 | 每页 ≤3 点、同批交付 | 单 change | 太碎合,逐页一套骨架 |
一句话收尾 :老页面改造,记住三句话------你只给锚点 + 增量 + 产物基准,别替 AI 抄现状 (现状 AI 自己读);只做增量,别顺手重构 (最小改动);新功能验收之外,老功能关键路径必过一遍(每次回归)。改老页面,先把「不动清单」写清楚,比写「改动方案」更重要。