6-13.【架构设计】什么是“单向数据流”?它解决的是什么问题?

0 阅读2分钟

一句话定义(不是教科书版)

单向数据流 = 状态只能被一种方式改变,变化只能沿一个方向传播。

换句话说:

State  ──▶  View  ──▶  Action  ──▶  State

没有:

  • View 直接改 State
  • A 改 B,B 又反改 A
  • “我也不知道是谁动了它”

一、单向数据流出现之前,世界有多糟?

典型的“双向地狱”(UIKit / MVC 常见)

ViewController
 ├── 改 Model
 ├── 监听 Model
 ├── 被 Model 回调
 ├── 又改 View
 └── View 又触发 Controller

问题不是“能不能跑”,而是:

❌ 你不知道:

  • 谁在什么时候改了数据
  • 改这个值会影响哪些 UI
  • 改一次为什么会触发三次刷新

真实工程痛点

  1. 状态源头不唯一
  2. 修改路径不可追踪
  3. Bug 只能靠断点撞
  4. 改一个功能,炸三个页面

二、单向数据流解决的核心问题(重点)

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 的“方向”强制为单向


五、它的真实代价是什么?

单向数据流不是免费午餐。

❌ 成本

  1. 样板代码增加
  2. 简单需求显得“重”
  3. Action / State 设计需要经验
  4. 一开始写得慢

✅ 但换来的是

  • 复杂系统仍然可理解
  • 人走了,系统还能维护
  • Bug 不会指数级增长

六、什么时候你“真的需要”单向数据流?

你可以用这条判断线:

“我是否已经开始害怕改代码?”

如果:

  • 一个状态被多个页面依赖
  • 异步 + UI 状态交织
  • Bug 难复现

👉 单向数据流已经不是“架构洁癖”,而是止血工具


最后一句话(架构级)

单向数据流不是为了优雅,
而是为了在复杂度失控前,给系统加一条‘唯一通道’。