Redux 中›ABC三个页面是如何通信的?

0 阅读4分钟

你的这种感觉非常正常!这也是很多初学者对 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()
            }
        }
    }
}

通信场景实现

现在,让我们看看如何实现具体的通信:

  1. 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,新的数据就显示出来了!
  2. B -> A 通信:(同上,方向相反)

  3. 任何页面 -> 全局状态

    • 任何页面都可以派发全局 Action:store.dispatch(.global(.loginSuccess))
    • 这会在根Reducer中处理,更新 state.userIsLoggedIn
    • 所有依赖 userIsLoggedIn 的页面都会自动更新!

总结

  • 只有一个 Store:这是 Redux 架构的绝对核心。
  • State 是组合的:每个页面的 State 是全局 AppState 的一个属性。
  • Action 是统一的:所有页面的 Action 都通过一个统一的枚举管理。
  • Reducer 是分形的:有一个根 Reducer,它负责将 Action 分发给各个页面的 Reducer 处理。
  • 通信方式:页面间通信就是派发一个目标为其他页面的 Action。这个 Action 会在根 Reducer 中被处理,并直接修改目标页面的 State

这种方式虽然初期需要更多样板代码,但带来的好处是巨大的:极其清晰的数据流、可预测的状态变化、易于调试和测试。所有页面间的耦合都被解除了,它们都只依赖于全局的 Store,而不是彼此。