6-14.【架构设计】在大型App中,全局State一定是反模式吗?什么情况下是灾难,什么情况下反而是最优解?

16 阅读5分钟

“全局 State 是不是反模式”本身就是一个误导性命题——真正的问题不是 全不全局,而是:

谁能改?怎么改?改了谁会受影响?


一句话结论(先给判断)

全局 State 本身不是反模式,
“可被任意修改的全局 State”才是。


一、为什么“全局 State”会被骂成反模式?

因为历史上它常常长这样👇

Singleton
 ├── 登录状态
 ├── 用户信息
 ├── 配置
 ├── 缓存
 ├── UI Flag
 └── 谁都能改
灾难不是“全局”,而是:
  • ❌ 修改入口无限多
  • ❌ 修改路径不可追踪
  • ❌ 没有边界、没有 owner
  • ❌ 改一个字段,全 App 都可能炸

二、什么情况下“全局 State = 灾难”?

1️⃣ 它承载“局部业务状态”

AppState.currentEditingPost
AppState.currentTab
AppState.isKeyboardVisible

❌ 这是典型灾难:

  • 生命周期短
  • 强 UI 相关
  • 与 Feature 强耦合

👉 本该是 局部 State,却被放成全局


2️⃣ 多个 Feature 直接读写

Feature A → 改
Feature B → 也改
Feature C → 监听副作用

结果:

  • 无法预测调用顺序
  • 并发条件竞争
  • Bug 难复现

3️⃣ 缺乏单向数据流约束

GlobalState.user = ...

谁改的?为什么改?什么时候改?

👉 调试直接进入“上帝模式”


4️⃣ 被用作“方便的捷径”

“先放全局,后面再重构”

这是大型 App 里技术债最凶的起点之一


三、那什么时候“全局 State”反而是最优解?

关键一句话:

当 State 的“语义就是全局的”,并且“变化路径是严格受控的”。


✅ 情况一:真正的 App 级事实(App Facts)

例如:

  • 登录 / 登出状态
  • 当前账号
  • Feature Flag / AB 实验
  • 语言 / 地区 / 时区
  • 远程配置版本

它们的特点:

  • 生命周期 = App 生命周期
  • 所有 Feature 都“合理依赖”
  • 改变时需要全 App 响应

👉 不全局反而更怪


✅ 情况二:只读为主、极少修改

读:高频
写:低频、可控

例如:

  • 配置
  • 权限矩阵
  • Capability 描述

👉 风险不在“读”,而在“写”。


✅ 情况三:有唯一写入口(极重要)

Action → Reducer → Global State

例如 TCA:

struct AppState {
    var auth: AuthState
    var settings: SettingsState
}
  • Feature 只能发 Action
  • 不能直接改 State

👉 全局 ≠ 失控


✅ 情况四:跨 Feature 协调状态

例如:

  • Deep Link
  • Push 跳转
  • 全局错误处理
  • Session 失效

它们天然是:

“一个事件 → 多个 Feature 联动”


四、判断一段 State 是否“该全局”的工程标准

你可以用这 4 个问题来问自己:

✅ 1️⃣ 生命周期是否 ≈ App 生命周期?

  • 是 → 倾向全局
  • 否 → 局部

✅ 2️⃣ 是否有“唯一可信来源”?

  • 是 → 可全局
  • 否 → 拆分

✅ 3️⃣ 修改是否必须可审计?

  • 是 → 强制单向数据流
  • 否 → 风险高

✅ 4️⃣ Feature 是否可以“只读”?

  • 可以 → 风险低
  • 必须写 → 需要边界

五、正确的“全局 State”长什么样?

✅ 好的全局 State(工程级)

struct AppState {
    var auth: AuthState
    var config: ConfigState
    var environment: EnvironmentState
}
  • 分域
  • 不承载 UI 细节
  • 不承载瞬时状态

❌ 坏的全局 State

struct AppState {
    var currentTab
    var isModalPresented
    var editingText
}

👉 UI 生命周期 ≠ App 生命周期。


六、SwiftUI / TCA 里的真实结论

全局 Store 是 TCA 的优势,而不是缺陷。
真正危险的是:没有 scope 的全局 Store。

scope(state:action:) 的意义不是解耦 View,
而是 给全局 State 划出“局部可见性”


最后一句话(架构级)

“全局”不是罪,
“无边界、无约束、无责任人”的全局,才是。

英文版

6-14. [Architecture Design] Is Global State Always an Anti-Pattern in Large Apps? When is it a Disaster, and When is it the Optimal Solution?

The question "Is Global State an anti-pattern?" is a misleading proposition in itself. The real issue isn't whether it is global or not, but rather:

Who can mutate it? How is it mutated? And who gets affected by those mutations?


The Bottom Line (The Verdict)

Global State itself is not an anti-pattern; "Global State that can be mutated arbitrarily" is.


I. Why is "Global State" often criticized as an Anti-Pattern?

Historically, it often looks like this 👇

The disaster isn't the "Global" scope; it's the lack of control:

  • Infinite mutation entries: Anyone can write to it.
  • Untraceable mutation paths: You don't know who changed what.
  • No boundaries or owners: Responsibility is scattered.
  • The Domino Effect: Changing one field might break the entire App.

II. When is "Global State" a Disaster?

1. Carrying "Local Business State"

Swift

AppState.currentEditingPost
AppState.currentTab
AppState.isKeyboardVisible

❌ This is a classic disaster:

  • Short lifecycle.
  • Highly UI-dependent.
  • Tightly coupled with specific Features. 👉 This should be Local State, but it's being "hoisted" globally.

2. Multiple Features Reading and Writing Directly

  • Feature A → Mutates
  • Feature B → Also Mutates
  • Feature C → Listens for side effects Result: Unpredictable calling order, race conditions, and non-reproducible bugs.

3. Lacking Unidirectional Data Flow Constraints

GlobalState.user = ... Who changed it? Why? When? 👉 Debugging becomes a "God Mode" nightmare.

4. Used as a "Convenient Shortcut"

"Let’s put it in global for now and refactor later."

In large apps, this is one of the most aggressive starting points for technical debt.


III. When is "Global State" the Optimal Solution?

The key principle:

When the semantics of the State are truly global, and the mutation path is strictly controlled.

✅ Case 1: True "App Facts"

Examples:

  • Login / Logout status.
  • The current Account/Session.
  • Feature Flags / AB Testing experiments.
  • Language / Locale / Timezone.
  • Remote Configuration versions.

Characteristics: Lifecycle = App Lifecycle; all features reasonably depend on them; changes require a whole-app response. 👉 It would be weirder if these weren't global.

✅ Case 2: Read-Heavy, Mutation-Light

Reading: High frequency | Writing: Low frequency, highly controlled. Examples: Permissions matrix, Capability descriptions, static configurations. 👉 The risk lies not in "Reading," but in "Writing."

✅ Case 3: A Single Entry Point for Writing (Critical)

Action → Reducer → Global State As seen in TCA:

Swift

struct AppState {
    var auth: AuthState
    var settings: SettingsState
}
  • Features can only send Actions.
  • They cannot directly mutate State. 👉 Global ≠ Out of Control.

IV. Engineering Standards: Should this State be Global?

Ask yourself these four questions:

  1. Is the lifecycle ≈ App lifecycle?

    • Yes → Lean toward Global.
    • No → Keep it Local.
  2. Is there a "Single Source of Truth"?

    • Yes → Potentially Global.
    • No → Split it.
  3. Must mutations be auditable?

    • Yes → Enforce Unidirectional Data Flow.
    • No → High risk.
  4. Can Features be "Read-Only"?

    • Yes → Low risk.
    • Must write → Needs a strict boundary.

V. What Does "Good" Global State Look Like?

✅ Good Global State (Engineering Grade)

Swift

struct AppState {
    var auth: AuthState
    var config: ConfigState
    var environment: EnvironmentState
}
  • Domain-partitioned.
  • No UI details.
  • No transient/ephemeral states.

❌ Bad Global State

Swift

struct AppState {
    var currentTab
    var isModalPresented
    var editingText
}

👉 UI lifecycle ≠ App lifecycle.


VI. The Conclusion in the context of SwiftUI / TCA

A Global Store is a strength of TCA, not a flaw. The real danger is a Global Store without "Scoping."

The purpose of scope(state:action:) is not just to decouple the View, but to define "Local Visibility" within the Global State.


Final Architectural Takeaway

"Global" is not a sin; Global that is "Boundless, Unconstrained, and Ownerless" is.