Skip to content

实战教程:重构一个 Vite 应用

本教程模拟一个常见 Issue:Vite 应用首页的“项目列表”组件越来越难维护,需要拆分数据逻辑与展示逻辑,同时保证用户行为不变。

你将练习的不是某个框架技巧,而是一套可以迁移到真实仓库的 Codex 协作方式:

text
复现 -> 建立基线 -> 约束范围 -> 分步重构 -> 针对性测试 -> 全量验证 -> 审查 diff

场景与验收标准

假设项目当前结构如下:

text
src/
  api/projects.ts
  components/ProjectList.vue
  pages/HomePage.vue
  types/project.ts
tests/
  ProjectList.spec.ts

ProjectList.vue 同时负责请求、加载状态、筛选、排序和渲染。Issue 要求:

  • 把请求和状态管理抽到 useProjects composable;
  • 保持现有 DOM 文案、排序规则和空状态不变;
  • 请求失败时增加可重试按钮;
  • 不增加依赖,不改变后端 API;
  • 原有测试继续通过,并补充失败重试测试。

IMPORTANT

重构的核心验收不是“代码更漂亮”,而是外部行为保持稳定。先确认行为基线,再移动职责。

第 0 步:保护现场

开始前记录仓库状态和现有验证命令:

bash
git status --short
cat package.json
pnpm test
pnpm build

如果基线已经失败,不要让 Codex默认把失败归因于本次修改。把现有失败的命令、错误摘要和是否允许处理写进任务说明。

推荐在独立分支或工作树进行练习:

bash
git switch -c codex/refactor-project-list

第 1 步:只调查,不修改

进入仓库后启动 Codex,并给出第一轮提示:

text
先不要修改文件。

请阅读:
- src/components/ProjectList.vue
- src/api/projects.ts
- src/types/project.ts
- tests/ProjectList.spec.ts

回答:
1. 当前数据从请求到渲染的完整路径;
2. 加载、成功、空数据和失败四种状态如何表现;
3. 哪些行为被测试保护,哪些没有;
4. 抽取 composable 时最容易引入的回归;
5. 建议的最小文件改动范围。

人工检查点

不要只看它是否“理解了”。核对下面几个事实:

  • 排序是在 API、组件还是计算属性中完成;
  • 组件是否在挂载时自动请求;
  • 测试是否依赖具体 DOM 文案或调用次数;
  • 是否有取消请求、竞态或组件卸载逻辑;
  • 错误状态是否已经存在,只是没有重试入口。

如果回答遗漏关键行为,用具体文件或测试纠偏,不要让错误假设进入计划。

第 2 步:要求一个可审查的计划

text
基于刚才的调查,给出一个不超过 5 步的实施计划。

每一步写清:
- 修改哪些文件;
- 保持什么行为不变;
- 增加或调整什么测试;
- 完成后运行哪条验证命令。

不要把格式化、依赖升级或其他组件清理混入本次重构。
如果需要改变公开 props、事件或 API,请先停下来说明。

一个合理计划通常是:

  1. 用现有测试固定排序、空状态和加载行为;
  2. 新建 useProjects.ts,迁移请求与状态但不接入页面;
  3. 接入 ProjectList.vue,保持模板契约;
  4. 增加失败与重试测试;
  5. 运行组件测试、全量测试和构建,审查 diff。

计划的价值是暴露风险和顺序,不是制造一份不能调整的长文档。

第 3 步:先补行为测试

如果现有测试没有保护排序或空状态,先让 Codex补基线:

text
先只修改 tests/ProjectList.spec.ts。

补充当前行为测试:
- 请求期间显示现有 loading 文案;
- 项目按当前规则排序;
- 空数组显示现有空状态;
- 不要为尚未实现的重试按钮写测试。

运行这个测试文件并报告结果。若测试暴露现有行为与我们的描述不一致,先停止并说明。

这一步能区分“重构导致的变化”和“原先理解错误”。测试应验证用户行为或稳定契约,避免锁死组件内部变量名。

第 4 步:抽取 composable

text
现在实现计划第 2 步:新建 src/composables/useProjects.ts。

要求:
- 复用现有 api/projects.ts 和 Project 类型;
- 暴露 projects、isLoading、error 和 load;
- 保持现有排序语义;
- 不修改 ProjectList.vue;
- 不新增依赖;
- 为 composable 增加最小必要测试。

完成后运行新增测试和类型检查,只报告与本步相关的改动。

分步提示的好处是 diff 小、失败定位快。此时 composable 还没接入 UI,即使设计不合适也容易调整。

第 5 步:接入组件并实现重试

text
将 ProjectList.vue 接入 useProjects。

保持:现有 props、事件、成功列表、排序、loading 和空状态文案。
新增:请求失败时显示错误状态和“重试”按钮;点击后再次调用 load;
重试期间按钮不可重复触发请求。

补充失败 -> 点击重试 -> 成功渲染的组件测试。
先运行相关测试;通过后再运行 lint 和类型检查。

为什么把重试放在最后

“抽取职责”和“新增行为”是两种风险。先完成等价重构,再增加错误重试,审查者可以分别判断结构变化与产品变化,回滚也更容易。

第 6 步:扩大验证范围

让 Codex运行验证,也要自己复核命令:

text
请执行交付前验证:
1. 相关组件和 composable 测试;
2. 全量单元测试;
3. lint 与类型检查;
4. production build。

不要修改代码来隐藏无关失败。逐条列出命令、退出结果和失败摘要。

前端任务还应人工验证:

  • 慢网下 loading 与按钮禁用状态;
  • 错误后重试是否只发送一次请求;
  • 空数据与失败状态是否容易混淆;
  • 375px 与桌面宽度是否溢出;
  • 浏览器控制台是否有警告或未处理 Promise。

第 7 步:用审查视角检查 diff

可以开启一个新的审查上下文,或使用当前版本支持的审查命令:

bash
codex review --help
git diff --check
git diff --stat
git diff

审查提示词:

text
审查当前分支相对 main 的改动。

重点检查:
- 加载、错误、重试是否存在竞态或重复请求;
- 组件卸载后是否可能更新状态;
- 排序和空状态是否发生兼容性变化;
- 测试是否会在错误实现下仍然通过;
- 是否有超出 Issue 范围的修改。

按严重性列出具体问题,给出文件与行号。纯风格偏好不要列为缺陷。

第 8 步:生成可用的 PR 摘要

text
根据最终 diff 和实际运行结果,生成 PR 描述,包含:
- 问题与目标;
- 设计选择;
- 用户可观察变化;
- 测试命令及结果;
- 风险、未验证项与回滚方式。

不要声称没有实际执行的测试已经通过。

推荐的交付结构:

md
## Summary
- Extract project loading into `useProjects`.
- Preserve existing list, sorting, loading, and empty states.
- Add an explicit error state with retry protection.

## Verification
- `pnpm test ProjectList` - passed
- `pnpm test` - passed
- `pnpm lint` - passed
- `pnpm build` - passed

## Risk
- Request cancellation on rapid route changes was not added; existing behavior is unchanged.
- Manual checks completed at 375px and 1440px.

失败时如何收缩任务

现象下一步
修改范围不断扩大回到行为基线,只允许修改一个模块及测试
测试失败很多且原因混杂先运行单个测试文件,再逐层扩大
Codex 猜测框架 API指向项目现有用法和锁定版本,不让它凭记忆迁移
为通过测试删除断言要求解释失败根因,恢复契约断言
diff 混入格式化单独还原无关格式变更,再继续审查
方案需要新依赖先比较现有能力、维护成本和包体积,再人工批准

从这个案例带走什么

  1. 重构前先固定外部行为,不把“看起来能跑”当基线;
  2. 调查、结构迁移和新增行为分开,降低每一步的不确定性;
  3. 提示词写清停止条件,遇到接口、依赖或范围变化先报告;
  4. 测试结果、构建结果和未验证项共同构成交付证据;
  5. 最终责任仍在审查者,Codex 的总结不能替代真实 diff。

资料来源

最后校对:2026 年 7 月 22 日