一、目标拆解
你希望 State 具备:
-
UI 可预测
- 每次 State 改变 → UI 渲染可预期
- 没有隐藏副作用,避免“偶发性闪烁/错误”
-
易调试
- 可以追踪“谁改了 State、什么时候改的、为什么改的”
-
易回滚 / 时间旅行
- 可以在历史状态间切换
- 用于 undo/redo 或测试
核心思想:State = 单一真相 + 单向数据流 + 可序列化
二、State 设计原则
1️⃣ 单一状态源(Single Source of Truth)
- 每个功能 Feature 有唯一 State 对象
- UI 只能观察 State,不直接改
- 所有修改通过 Action → Reducer / Effect
UI → Action → Reducer → State → UI
- 避免双向绑定和多处修改
- UI 可预测性高
2️⃣ 不可变 / 派生分离
- State 尽量不可变(Struct)
- 派生状态不存储在 State 内,只依赖原始 State 计算
struct AppState {
var users: [User] // ✅ State
var searchText: String // ✅ State
// totalPrice 不存储,而是计算
}
var filteredUsers: [User] {
users.filter { $0.name.contains(searchText) }
}
-
好处:
- 避免双源真相
- UI 可预测
- 易调试(只关心真实 State)
- 可回滚(snapshot 轻量)
3️⃣ 小而扁平,粒度可控
- 不要把整个 Feature 的状态塞成一坨嵌套对象
- 结构扁平、按逻辑分域
- 派生 / 缓存数据放 UI Adapter / ViewModel 层
struct OrderState {
var items: [Item]
var selectedItemID: UUID?
}
- 每个字段变化 → 最小化 UI 失效半径
- 高频字段放在最下层
4️⃣ Action 可序列化
-
所有状态修改都通过 Action
-
Action 应该是可枚举 / 可序列化
-
便于:
- 调试 trace
- 时间旅行 / undo
enum OrderAction {
case addItem(Item)
case removeItem(UUID)
case selectItem(UUID?)
}
5️⃣ Reducer 是纯函数
func orderReducer(state: inout OrderState, action: OrderAction) {
switch action {
case .addItem(let item):
state.items.append(item)
case .removeItem(let id):
state.items.removeAll { $0.id == id }
case .selectItem(let id):
state.selectedItemID = id
}
}
-
不依赖外部环境
-
输入相同 Action + State → 输出总是相同
-
便于:
- 可预测 UI
- 易调试
- 可回滚
6️⃣ 副作用隔离
- 不直接在 State / Reducer 中执行网络 / DB / 定时任务
- 使用 Effect / async Action
- 副作用触发 Action → 更新 State
Task {
let data = await api.fetch()
store.send(.dataLoaded(data))
}
- 避免“State 一半是 UI,一半是业务副作用”
- 提高可预测性
三、工程落地策略
1️⃣ 版本化 / 快照
- TCA 提供
Store可以 snapshot - Redux 可用
time-travel debugger - 可调试、可回滚
let snapshot = store.state
// …操作…
store.restore(snapshot)
2️⃣ State 下沉 / 局部化
- 高频变化字段放局部 View / Row State
- 减少 diff / 渲染开销
- 依然保留全局真相作为 Feature State
3️⃣ 派生 State 计算优化
- 对昂贵 Derived State 使用 memoization 或放 ViewModel 缓存
- 保证 State 本身轻量
- 不破坏 UI 可预测性
4️⃣ Action Logging / Trace
- 每个 Action 发出 → 打日志
- 可以重放动作
- 配合 Snapshot → 时间旅行
四、设计心智公式
State = minimal + immutable + serializable
Action → Reducer =唯一修改入口
Derived State = computed or cached near UI
副作用 = Effect / async Action
- 可预测 UI → 单向数据流 + 纯 Reducer
- 易调试 → Action 日志 + Snapshot
- 可回滚 → Immutable State + snapshot
五、真实工程示例
假设一个购物车 Feature:
struct CartState {
var items: [Item]
var selectedItemID: UUID?
}
enum CartAction {
case addItem(Item)
case removeItem(UUID)
case selectItem(UUID?)
case checkout
}
func cartReducer(state: inout CartState, action: CartAction) {
switch action {
case .addItem(let item):
state.items.append(item)
case .removeItem(let id):
state.items.removeAll { $0.id == id }
case .selectItem(let id):
state.selectedItemID = id
case .checkout:
break
}
}
- 派生状态:总价 totalPrice 通过
state.items.reduce(...)计算 - UI 观察
CartState→ List / selection 自动更新 - Action 日志 + Snapshot → 可调试 + 可回滚
六、总结一句话
State = 系统事实的单一真相 + 不可变 + 受控修改,
Derived State = 从真相即时计算,副作用隔离,Action → Reducer → State 流程串行化,
这样 UI 可预测、易调试、可回滚。