一句话定义(不是教科书版)
单向数据流 = 状态只能被一种方式改变,变化只能沿一个方向传播。
换句话说:
State ──▶ View ──▶ Action ──▶ State
没有:
- View 直接改 State
- A 改 B,B 又反改 A
- “我也不知道是谁动了它”
一、单向数据流出现之前,世界有多糟?
典型的“双向地狱”(UIKit / MVC 常见)
ViewController
├── 改 Model
├── 监听 Model
├── 被 Model 回调
├── 又改 View
└── View 又触发 Controller
问题不是“能不能跑”,而是:
❌ 你不知道:
- 谁在什么时候改了数据
- 改这个值会影响哪些 UI
- 改一次为什么会触发三次刷新
真实工程痛点
- 状态源头不唯一
- 修改路径不可追踪
- Bug 只能靠断点撞
- 改一个功能,炸三个页面
二、单向数据流解决的核心问题(重点)
1️⃣ 状态修改“可追溯”
在单向数据流中:
任何 State 改变
⬇
必然来自某个 Action
⬇
经过某个 Reducer / Handler
👉 你永远可以回答:
“是谁、在什么时候、因为什么,改了这个状态?”
2️⃣ 消灭“隐式耦合”
没有:
- View 偷偷改 Model
- Model 回调又触发别的 Model
- 状态在多个地方被同步
只有:
send(.addItem)
3️⃣ 极大降低认知负担
你调试时的心智模型从:
“所有对象可能互相影响”
变成:
“Action → State → UI”
4️⃣ 天然支持可测试
因为:
- Reducer 是纯函数
- 输入 Action
- 断言 State
func testAddItem() {
var state = State()
reducer(&state, .addItem)
XCTAssertEqual(state.items.count, 1)
}
不用:
- View
- 线程
- 模拟点击
5️⃣ 为并发和异步提供“收敛点”
异步不再“满天飞”:
Network / Timer / Notification
↓
Action
↓
State
👉 所有并发结果最终都 串行汇入 State。
三、为什么 SwiftUI / TCA / Redux 都强制单向?
因为它们假设:
UI 是状态的函数
UI = f(State)
如果 State 能被任意方向修改:
- diff 不可信
- 渲染不可预测
- 性能优化无从谈起
四、单向数据流 vs 传统 MVVM
| 传统 MVVM | 单向数据流 | |
|---|---|---|
| 状态修改 | 双向绑定 | Action |
| 修改入口 | 多个 | 单一 |
| 调试 | 靠经验 | 可回放 |
| 并发 | 易竞态 | 可控 |
TCA = 把 MVVM 的“方向”强制为单向
五、它的真实代价是什么?
单向数据流不是免费午餐。
❌ 成本
- 样板代码增加
- 简单需求显得“重”
- Action / State 设计需要经验
- 一开始写得慢
✅ 但换来的是
- 复杂系统仍然可理解
- 人走了,系统还能维护
- Bug 不会指数级增长
六、什么时候你“真的需要”单向数据流?
你可以用这条判断线:
“我是否已经开始害怕改代码?”
如果:
- 一个状态被多个页面依赖
- 异步 + UI 状态交织
- Bug 难复现
👉 单向数据流已经不是“架构洁癖”,而是止血工具。
最后一句话(架构级)
单向数据流不是为了优雅,
而是为了在复杂度失控前,给系统加一条‘唯一通道’。