在成熟的 SwiftUI 项目中,引入像 TCA (The Composable Architecture) 这样具有极强侵入性的架构,最忌讳的是“推倒重来”。
成功的策略应该是**“边缘突破、局部隔离、渐进渗透”**。以下是分阶段引入的路线图:
第一阶段:组件解耦(解铃还须系铃人)
在引入新架构前,先清理现有的 View。如果你的 View 逻辑和 UI 混在一起,任何架构迁移都会失败。
- 提取纯视图(Stateless Views) :将复杂的 View 拆解为仅接收参数的子视图。
- 消除硬编码依赖:确保 View 不直接调用单例(如
UserCenter.shared),而是通过属性注入。 - 目的:为后续将“逻辑”装入 TCA Reducer 或 MVVM ViewModel 准备好干净的“容器”。
第二阶段:局部试点(建立“特区”)
选择一个业务逻辑独立、 UI 路径清晰的边缘模块(例如:设置页面、搜索列表)作为试点。
如果引入 MVVM:
- 为该 View 创建一个独立的
ObservableObject类。 - 将视图内所有的
@State搬移到 ViewModel 的@Published中。 - 桥接技巧:如果父视图需要这个子视图的数据,通过
Binding进行双向绑定,不要在第一步就试图改变父视图的架构。
如果引入 TCA:
- 创建独立 Store:在这个子模块内部定义
State、Action和Reducer。 - 手动托管:在现有的 View(可能是普通的 SwiftUI View)中,用
@State显式持有一个Store实例。 - 防御点:不要急着把这个 Store 放入全局环境。让它像一个黑盒,只负责处理这个小页面的逻辑。
第三阶段:建立“胶水层”(双架构并存)
项目此时会进入“混合动力”时期:旧的命令式代码、MVVM 逻辑和 TCA 并存。
-
TCA 与 MVVM 互操作:
如果你需要在现有的 ViewModel 中触发 TCA 的逻辑,可以利用 Effect。在 ViewModel 里持有 Store,并通过发送 Action 来驱动状态。
-
状态同步的权衡:
不要试图在两个架构间实现实时同步。以“导航切换”作为界限。当用户进入 TCA 模块时,将数据快照传入;当用户离开时,通过闭包或通知将结果回传给旧模块。
第四阶段:自下而上的合并(Scope & Composition)
随着 TCA 模块增多,你会发现可以开始整合它们。
- 利用
Scope:将两个独立的 TCA 功能组合成一个更大的 Reducer。 - 向上渗透:原本由
@StateObject管理的局部逻辑,逐渐被上层的父 Store 接管。 - 物理隔离:将成熟的架构模块(TCA 或 MVVM)移动到独立的 SPM Target 中。这样可以利用编译器的强制边界,防止旧代码的反向污染。
避坑指南:渐进式重构的 3 个“不”
- 不要尝试“全局全局变量”映射:不要试图把现有的全局环境对象(
EnvironmentObject)直接映射进 TCA 的全局 State,这会导致复杂的同步 Bug。让 TCA 拥有自己的依赖系统(Dependency) 。 - 不要为了架构而架构:简单的静态页面(如“关于我们”)不需要 TCA 或繁重的 ViewModel,保持简单的
@State即可。 - 不要在重构时增加新功能:这会导致测试用例失效,无法判断是架构改错了还是业务逻辑写错了。
总结:你的重构核对表
| 阶段 | 核心任务 | 成功标志 |
|---|---|---|
| 初期 | 拆分 View | View 可以在 Preview 中不传 Mock 实例就跑通。 |
| 中期 | 局部封装 | 新模块的 Bug 不会波及旧模块,反之亦然。 |
| 后期 | 组合挂载 | 子 Store 的状态能够被父 Store 通过 pullback (TCA) 追踪。 |