6-15.【架构设计】State和Derived State(派生状态)应该如何区分?错误区分会带来哪些 一致性和性能问题?

0 阅读3分钟

这是一个所有“状态驱动 UI”体系的核心问题
如果把它搞清楚,SwiftUI / TCA / Redux / Jetpack Compose 的 80% 性能与一致性问题都会自动消失

  1. 什么是 State vs Derived State(工程定义)
  2. 最小判定法则(实用)
  3. 错误区分会导致的“一致性灾难”
  4. 错误区分导致的“性能陷阱”
  5. SwiftUI / TCA 中的落地策略

一、先给一句工程级定义(不是教科书)

✅ State(源状态)

State = 系统必须“记住”的最小事实集合

  • 不可从其他状态推导
  • 丢失就无法恢复
  • 生命周期明确

✅ Derived State(派生状态)

Derived State = 从 State 计算得出的视图或业务投影

  • 可 100% 从 State 重新算出
  • 不需要被“记住”
  • 只为方便使用 / 性能
State ──计算──▶ Derived State

二、一个“不会错”的判定法则(强烈建议背下来)

如果删掉这个字段,只靠其他 State 能 100% 还原它,它就不是 State。


三、经典错误一:把 Derived State 当成 State(最常见)

❌ 错误示例

struct State {
    var items: [Item]
    var totalPrice: Double   // ❌ Derived
}

totalPrice 明明可以:

items.reduce(0) { $0 + $1.price }

一致性灾难如何发生?

Action A → 改 items
❌ 忘了改 totalPrice

或者:

Action B → 改 totalPrice
❌ items 没变

结果:

  • UI 显示错
  • Bug 偶现
  • Debug 极难

👉 双源真相(Two Sources of Truth)


四、经典错误二:把 State 当 Derived State(隐蔽但致命)

❌ 错误示例

var filteredUsers: [User] {
    users.filter { $0.name.contains(searchText) }
}

然后在 body 里:

List(filteredUsers) { ... }

看似没问题,但:

  • filter 很重
  • 每次 diff 都会算
  • 输入就卡

五、错误区分导致的两类灾难


🧨 灾难一:一致性问题(逻辑层)

症状

  • 同一数据在不同页面值不同
  • UI 偶尔“自己修复”
  • 重进页面就好了

根因

Derived State 被持久化,且更新路径不唯一


🧨 灾难二:性能问题(渲染层)

症状

  • SwiftUI 输入卡顿
  • List 滚动掉帧
  • CPU 异常

根因

Derived State 在 body / 高频路径中重复计算


六、那 Derived State 到底应该放哪?

答案不是“不要”,而是:

放在“离使用者最近,但不参与一致性”的地方


策略一:即时计算(轻量)

var isValid: Bool {
    email.contains("@")
}
  • 计算便宜
  • 不存

策略二:缓存为局部 State(性能优化)

@State private var filtered: [User] = []

.onChange(of: searchText) {
    filtered = users.filter(...)
}

👉 Derived State,但显式缓存


策略三:ViewModel / Adapter 层

final class ViewModel {
    let users: [User]
    var filteredUsers: [User] { ... }
}
  • SwiftUI body 干净
  • 性能可控

策略四:TCA 中的 computed / Selector

var totalPrice: Double {
    state.items.reduce(...)
}

或 memoized selector。


七、SwiftUI 里的黄金规则(非常重要)

@State 只能用于“用户直接操纵的最小事实”

不是:

  • 显示文本
  • 排序结果
  • 是否可点击(通常 derived)

而是:

  • 输入值
  • 选择项
  • 展开 / 折叠
  • 当前焦点

八、一个真实“修复即提速”的案例

❌ 改前

@State var sortedItems: [Item]
  • 每次数据源变化都要同步
  • 状态错乱

✅ 改后

var sortedItems: [Item] {
    items.sorted(by: ...)
}

或缓存到 ViewModel。

👉 一致性消失 + diff 成本下降。


九、终极心法(适用于任何架构)

State 越少,系统越稳定;
Derived State 越靠近消费端,系统越快。


最后一句话(架构级)

State 是“必须记住的事实”,
Derived State 是“可以随时算出的观点”。
混淆它们,系统就会同时变慢、变乱、变不可维护。