维护性重构 — frontend/src/pages 模块化¶
状态: 计划(尚未执行) · 起草日期: 2026-06-30 · 类型: 纯机械性重构,无行为变更
为什么¶
frontend/src/pages/ 中有些文件已增长到相当大的规模,任何编辑都会把整个文件重新加载到上下文中,并带来无关破坏的风险。编辑某个 Settings 面板时,应该只需改动一个约 150 行的文件,而不是一个 1969 行的文件。这既能提高可维护性,也能降低每次编辑的 token 成本。
这种修复不是一种新架构——components/settings/ 已经证明了目标模式(13 个已抽取的 *Panel.jsx,每个都自带同目录的 .css/.test.jsx,外加一个共享的 primitives/ 文件夹)。本次重构完成了一项停滞的迁移,然后将其固化,避免文件悄悄膨胀回原状。
当前状态(2026-06-30 实测)¶
| 文件 | 行数 | 备注 |
|---|---|---|
pages/Settings.jsx |
1969 | 仍然内联:ModelStoreTab(约 790 行)、Settings 编排器(约 600 行)、GeneralTab、EnginesTab、HotkeyTab、CredentialsTab,以及 Row/fmtBytes/orgColor 辅助函数 |
pages/DubTab.jsx |
1592 | 一个巨型组件 + 内联的 DubFailureNotice、DubPipelineStepper、PrepOverlay、TranscribeOverlay、FooterBtn |
pages/CloneDesignTab.jsx |
837 | |
pages/VoiceGallery.jsx |
768 | |
pages/VoiceProfile.jsx |
515 | |
pages/AudiobookTab.jsx |
402 | 在阶段 3 清理后处于目标范围内 |
| 其他所有文件 | <340 | 在目标范围内 |
已抽取的部分不要动(参考模式):components/settings/*Panel.jsx、components/settings/primitives/。
黄金标准(提议)¶
- 大小上限: 每个
.jsx/.css软上限 300 行,硬上限 500 行。超过 500 行必须拆分。 - 页面应是薄编排器: 页面 = 布局 + 路由 + 状态接线,并组合功能组件。不允许任何内联子组件超过约 50 行。
- 每个文件一个组件,同目录放置
Foo.jsx+Foo.css+Foo.test.jsx,并按页面的功能文件夹分组: components/settings/(已存在)components/dub/(新建)components/clone/(新建)components/gallery/(新建)- 共享部分 →
primitives/,放在功能文件夹内(settings 已经这样做了)。 - 用 ESLint 的
max-lines来强制——先仅告警(warn-only),这样它永远不会破坏 CI(遵循“保持主分支绿色”的规则),待积压清理完成后升级为错误。
阶段(每个阶段 = 一个可合并、CI 通过的 PR)¶
阶段 0 — 规范 + 护栏¶
- 将大小/结构规则添加到
CONTRIBUTING.md(反正文档同步规则也要求这样做)。 - 添加 ESLint 配置
max-lines: ['warn', { max: 500, skipBlankLines: true, skipComments: true }]。 - 不移动任何代码。尽可能小的 PR;建立约定。
阶段 1 — Settings.jsx(最大收益:1969 → 约 300 行)¶
抽取到 components/settings/ 中,沿用现有面板命名方式:
| 抽取内容 | 当前行数(约) | 新文件 |
|---|---|---|
ModelStoreTab(+ Row、fmtBytes、orgColor、MODEL_ROLE_*) |
229–1021 | ModelStoreTab.jsx(可能进一步拆分:表格 vs 矩阵 vs 行) |
GeneralTab |
80–201 | GeneralTab.jsx |
EnginesTab |
1022–1072 | EnginesTab.jsx |
HotkeyTab(+ CREDENTIAL_FIELDS、keyEventToAccelerator) |
1693–1870 | HotkeyTab.jsx |
CredentialsTab |
1871–1969 | CredentialsTab.jsx |
Settings.jsx 仅保留:imports、TAB_DEFS/LOG_SOURCE_DEFS、Settings 默认导出(标签页路由 + 共享状态),以及 askConfirm。
阶段 2 — DubTab.jsx(1592 → 编排器 + components/dub/)¶
将 DubFailureNotice、DubPipelineStepper、PrepOverlay、TranscribeOverlay、FooterBtn 以及较大的渲染子区块抽取到 components/dub/。DubTab.jsx 保留流水线状态机 + 组合逻辑。
阶段 3 — CloneDesignTab、VoiceGallery、VoiceProfile、AudiobookTab¶
对它们执行同样的处理,放入 components/clone/ 和 components/gallery/。规模更小,风险更低。
遵循的约束¶
- 无行为变更 —— 纯移动;可通过“应用渲染结果一致 + 现有测试通过”来验证 diff。每个有测试的面板都保留其测试。
- 保持主分支绿色 —— ESLint 规则为仅告警;每个阶段都独立保持 CI 绿色。
- 文档同步 —— 阶段 0 将
CONTRIBUTING.md的改动与规则放在同一个 PR 中。 - 无版本化影响 —— 仅前端重构;不提升
package.json版本,不更改 lockfile/依赖,不触及 Docker/Tauri/Python。
每个阶段的验证¶
bun run build(或项目的 typecheck/lint)通过。- 现有的
components/settings/*.test.jsx(以及任何新的同目录测试)通过。 - 手动冒烟测试:打开 Settings → 每个标签页都能渲染;打开 Dub → 流水线能渲染。
git diff --stat显示只有移动(行数在文件之间变化,逻辑变更净约为 0)。
明确不在范围内¶
- 不重新设计 Settings UI 本身(“不整齐”的外观)——那是另一项视觉打磨任务;本次重构只调整代码结构。如果你希望一并处理,请标记出来。
- 不进行
.jsx→.tsx转换(页面目前是 JS;TS 迁移是另一个单独的决策)。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 om/VoiceStudio