你的这种感觉非常正常!这也是很多初学者对 Redux 最大的误解。如果 A、B、C 三个页面分别有自己的 Store,那你就完全违背了 Redux 最核心的“单一数据源”原则,自然会陷入无法通讯的困境。
Redux 的核心理念是:整个应用有且只有一个全局 Store。A、B、C 三个页面共享这个唯一的 Store,而不是各自拥有一个。
让我用正确的 Redux 思维来为你重构这个问题,你会发现通讯变得非常简单和清晰。
正确的 Redux 结构:单一数据源
flowchart TD
AppState["全局 AppState<br>包含三个页面的数据"]
subgraph A [页面A]
A_State[StateA]
A_Action[ActionA]
end
subgraph B [页面B]
B_State[StateB]
B_Action[ActionB]
end
subgraph C [页面C]
C_State[StateC]
C_Action[ActionC]
end
AppState --> A_State
AppState --> B_State
AppState --> C_State
A_Action -- dispatch --> Store
B_Action -- dispatch --> Store
C_Action -- dispatch --> Store
Store -- 更新 --> AppState
实现步骤
第 1 步:定义全局的 State、Action 和 Reducer
State.swift - 单一数据源
// 整个应用只有一个根状态
struct AppState {
// 页面A的状态,只是这个根状态的一个属性
var pageAState: PageAState
// 页面B的状态
var pageBState: PageBState
// 页面C的状态
var pageCState: PageCState
// 还可以有跨页面的共享状态
var userIsLoggedIn: Bool
}
// 每个页面的状态仍然是独立的结构体,但被整合到AppState中
struct PageAState {
var dataForA: String = ""
var valueFromB: String? = nil // 用于接收来自B的数据
}
struct PageBState {
var dataForB: Int = 0
var valueFromC: String? = nil // 用于接收来自C的数据
}
struct PageCState {
var dataForC: [String] = []
}
Action.swift - 统一的行为定义
// 所有页面的Action都集中在一个枚举中
enum AppAction {
// 页面A的Action
case pageA(PageAAction)
case pageB(PageBAction)
case pageC(PageCAction)
// 全局的Action,如登录、登出
case global(GlobalAction)
}
// 每个页面自己的Action枚举
enum PageAAction {
case buttonTapped
case dataLoaded(String)
case receivedDataFromB(String) // 专门用于接收B的消息
}
enum PageBAction {
case sliderValueChanged(Int)
case sendDataToA(String) // 专门用于向A发送数据
}
enum PageCAction {
case itemSelected(Int)
}
Reducer.swift - 统一的 reducer
// 根Reducer,负责组合所有页面的reducer
func appReducer(state: inout AppState, action: AppAction) -> Void {
switch action {
// 分解处理页面A的Action
case .pageA(let pageAAction):
pageAReducer(state: &state.pageAState, action: pageAAction)
// 分解处理页面B的Action
case .pageB(let pageBAction):
pageBReducer(state: &state.pageBState, action: pageBAction)
// B的Action可能会影响到其他页面!
// 例如:当B发送数据时,需要更新A的状态
if case .sendDataToA(let data) = pageBAction {
state.pageAState.valueFromB = data // 直接修改A的状态
}
// 分解处理页面C的Action
case .pageC(let pageCAction):
pageCReducer(state: &state.pageCState, action: pageCAction)
// 处理全局Action
case .global(let globalAction):
globalReducer(state: &state, action: globalAction)
}
}
// 每个页面自己的reducer(纯函数)
func pageAReducer(state: inout PageAState, action: PageAAction) {
switch action {
case .buttonTapped:
print("A的按钮被点击")
case .dataLoaded(let data):
state.dataForA = data
case .receivedDataFromB(let dataFromB):
state.valueFromB = dataFromB // 更新来自B的数据
}
}
func pageBReducer(state: inout PageBState, action: PageBAction) {
switch action {
case .sliderValueChanged(let value):
state.dataForB = value
case .sendDataToA(let data):
// 注意:这个Action的主要处理逻辑在根Reducer中
// 这里可以处理B自身相关的状态更新
print("B准备发送数据给A: \(data)")
}
}
第 2 步:创建唯一的全局 Store
Store.swift
class Store: ObservableObject {
@Published private(set) var state: AppState
private let reducer: (inout AppState, AppAction) -> Void
init(initialState: AppState, reducer: @escaping (inout AppState, AppAction) -> Void) {
self.state = initialState
self.reducer = reducer
}
func dispatch(_ action: AppAction) {
reducer(&state, action)
}
}
// 在应用入口创建唯一Store
let globalStore = Store(initialState: AppState(
pageAState: PageAState(),
pageBState: PageBState(),
pageCState: PageCState(),
userIsLoggedIn: false
), reducer: appReducer)
第 3 步:在页面中使用全局 Store
PageAView.swift
struct PageAView: View {
@EnvironmentObject var store: Store // 注入的是全局唯一的Store
// 从全局State中取出页面A需要的部分状态
private var pageAState: PageAState { store.state.pageAState }
var body: some View {
VStack {
Text("页面A的数据: \(pageAState.dataForA)")
// 显示从页面B传来的数据
if let dataFromB = pageAState.valueFromB {
Text("来自B的消息: \(dataFromB)")
}
Button("通知B") {
// 派发Action,而不是直接调用B的方法
store.dispatch(.pageB(.sendDataToA("你好,我是A!")))
}
}
}
}
PageBView.swift
struct PageBView: View {
@EnvironmentObject var store: Store // 同一个全局Store
private var pageBState: PageBState { store.state.pageBState }
var body: some View {
VStack {
Text("B的数值: \(pageBState.dataForB)")
Button("发送数据到A") {
// 通过全局Store派发Action
store.dispatch(.pageB(.sendDataToA("Hello from B!")))
}
NavigationLink("去C") {
PageCView()
}
}
}
}
通信场景实现
现在,让我们看看如何实现具体的通信:
-
A -> B 通信:
- A 中:
store.dispatch(.pageB(.sendDataToA("你好,我是A!")))
- 根Reducer 接收到
AppAction.pageB(.sendDataToA)
,它会: a. 调用pageBReducer
处理 B 自身的状态(如果需要) b. 直接修改state.pageAState.valueFromB
- 由于 PageAView 依赖于
store.state.pageAState
,SwiftUI 会自动重绘页面A,新的数据就显示出来了!
- A 中:
-
B -> A 通信:(同上,方向相反)
-
任何页面 -> 全局状态:
- 任何页面都可以派发全局 Action:
store.dispatch(.global(.loginSuccess))
- 这会在根Reducer中处理,更新
state.userIsLoggedIn
- 所有依赖
userIsLoggedIn
的页面都会自动更新!
- 任何页面都可以派发全局 Action:
总结
- 只有一个 Store:这是 Redux 架构的绝对核心。
- State 是组合的:每个页面的 State 是全局 AppState 的一个属性。
- Action 是统一的:所有页面的 Action 都通过一个统一的枚举管理。
- Reducer 是分形的:有一个根 Reducer,它负责将 Action 分发给各个页面的 Reducer 处理。
- 通信方式:页面间通信就是派发一个目标为其他页面的 Action。这个 Action 会在根 Reducer 中被处理,并直接修改目标页面的 State。
这种方式虽然初期需要更多样板代码,但带来的好处是巨大的:极其清晰的数据流、可预测的状态变化、易于调试和测试。所有页面间的耦合都被解除了,它们都只依赖于全局的 Store,而不是彼此。