一、目标拆解
你希望 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 可预测、易调试、可回滚。
英文版
[Architecture Design] How to Design State for Predictable UI, Easy Debugging, and Seamless Rollbacks
I. Goal Decomposition
To achieve high-quality architecture, your State must support:
-
Predictable UI
- Every State change → UI renders exactly as expected.
- No hidden side effects; elimination of "sporadic glitches/flickers."
-
Easy Debugging
- The ability to trace: "Who changed the State, when, and why?"
-
Easy Rollbacks / Time Travel
- Switching between historical states.
- Essential for Undo/Redo functionality or automated testing.
Core Philosophy: State = Single Source of Truth + Unidirectional Data Flow + Serializability
II. State Design Principles
1️⃣ Single Source of Truth (SSoT)
- Each feature has a unique State object.
- The UI only observes the State; it never modifies it directly.
- All modifications pass through Action → Reducer / Effect.
- Flow:
UI → Action → Reducer → State → UI - Benefit: Prevents two-way binding conflicts and "out-of-sync" bugs.
2️⃣ Immutability & Derived Separation
- Keep the State Immutable (using Structs).
- Derived State should not be stored; it should be calculated based on the raw State.
- Benefits: Prevents "dual sources of truth," ensures UI predictability, simplifies debugging, and makes snapshots lightweight for rollbacks.
3️⃣ Small, Flat, and Granular
- Avoid nesting massive objects into a single "blob."
- Keep the structure flat and domain-partitioned.
- Move derived/cached data to the UI Adapter or ViewModel layer.
- Benefit: Minimizes the "invalidation radius" of UI updates; high-frequency fields remain isolated.
4️⃣ Serializable Actions
- Every state change occurs via an Action.
- Actions should be Enums and ideally serializable.
- Enables: Trace debugging, time travel, and undo/redo logs.
5️⃣ Pure Function Reducers
- Reducers must not depend on the external environment.
- Same Action + Same State = Same Output.
- Enables: Testing, deterministic UI, and reliable rollbacks.
6️⃣ Side Effect Isolation
- Do not execute Network, DB, or Timer tasks directly inside the State or Reducer.
- Use Effects / Async Actions.
- Effects trigger Actions → Actions update State.
- Benefit: Prevents "leaky" states where UI and business logic side effects become entangled.
III. Engineering Implementation Strategies
-
Versioning / Snapshotting
- TCA (The Composable Architecture) provides a
Storethat can be snapshotted. - Redux-like patterns allow for
time-travel debuggers.
- TCA (The Composable Architecture) provides a
-
State Scoping / Localization
- Place high-frequency fields in local View/Row States.
- Reduces diffing/rendering overhead while maintaining the global truth.
-
Derived State Optimization
- Use memoization or cache expensive Derived State in the ViewModel.
- Keeps the core State lightweight without breaking predictability.
-
Action Logging & Tracing
- Log every Action as it is dispatched.
- Replay actions alongside Snapshots to achieve "Time Travel."
IV. Design Mental Formula
State = Minimal + Immutable + Serializable
Action → Reducer = The sole entry point for modification
Derived State = Computed or cached near the UI
Side Effects = Isolated via Effects / Async Actions
- Predictable UI ← Unidirectional Data Flow + Pure Reducers
- Easy Debugging ← Action Logs + Snapshots
- Rollbacks ← Immutable State + Snapshots
V. Real-World Example: Shopping Cart
- Derived State:
totalPriceis calculated on the fly fromitems. - UI: Observes
CartState; the List and selection update automatically. - Rollback: Simply re-assigning a previous
CartStatestruct resets the entire UI instantly.
VI. Final Summary
State is the Single Source of Truth for system facts—immutable and modified only through controlled channels. Derived State is computed instantly from that truth, side effects are isolated, and the Action → Reducer → State flow is serialized. This is how you build a UI that is predictable, debuggable, and reversible.