SwiftUI Redux 中子 Reducer 调用其他 Reducer 的方法

152 阅读2分钟

SwiftUI Redux 中子 Reducer 调用其他 Reducer 的方法

在 SwiftUI Redux 架构中,有几种常见的方式来实现子 Reducer 之间的通信和调用。

1. 通过 Action 链式调用

这是最符合 Redux 哲学的方式,通过分发 Action 来触发其他 Reducer:

// 定义 Action
enum AppAction {
    case user(UserAction)
    case settings(SettingsAction)
    case crossModuleAction(CrossModuleAction)
}

enum CrossModuleAction {
    case userUpdatedAndNeedSettingsRefresh
}

// 主 Reducer
func appReducer(state: inout AppState, action: AppAction) -> Void {
    switch action {
    case .user(let userAction):
        userReducer(state: &state.user, action: userAction)
        
        // 检查是否需要触发其他模块的 Action
        if case .userProfileUpdated = userAction {
            // 分发跨模块 Action
            return appReducer(state: &state, action: .crossModuleAction(.userUpdatedAndNeedSettingsRefresh))
        }
        
    case .settings(let settingsAction):
        settingsReducer(state: &state.settings, action: settingsAction)
        
    case .crossModuleAction(let crossAction):
        handleCrossModuleAction(state: &state, action: crossAction)
    }
}

// 处理跨模块 Action
func handleCrossModuleAction(state: inout AppState, action: CrossModuleAction) {
    switch action {
    case .userUpdatedAndNeedSettingsRefresh:
        // 调用 settings reducer 的刷新方法
        settingsReducer(state: &state.settings, action: .refreshBasedOnUserChange)
    }
}

2. 使用 Effect 系统(类似 Redux-Observable)

实现一个 Effect 系统来处理副作用和跨 Reducer 调用:

struct Effect<Action> {
    let run: (@escaping (Action) -> Void) -> Void
}

// Reducer 返回 (state, effect) 元组
typealias Reducer<State, Action> = (inout State, Action) -> [Effect<Action>]

func userReducer(state: inout UserState, action: UserAction) -> [Effect<AppAction>] {
    switch action {
    case .loginSuccess:
        state.isLoggedIn = true
        // 返回一个 effect 来触发其他 reducer
        return [Effect { dispatch in
            dispatch(.settings(.loadUserSpecificSettings))
            dispatch(.notifications(.setupForUser)))
        }]
    default:
        return []
    }
}

3. 组合式 Reducer 设计

创建可以相互调用的组合式 Reducer:

protocol Reducible {
    associatedtype State
    associatedtype Action
    static func reduce(state: inout State, action: Action) -> SideEffects<Action>
}

enum SideEffects<Action> {
    case none
    case effect(Action)
    case effects([Action])
    
    func map<T>(_ transform: (Action) -> T) -> SideEffects<T> {
        // 实现 map 逻辑
    }
}

// 用户模块 Reducer
enum UserReducer: Reducible {
    static func reduce(state: inout UserState, action: UserAction) -> SideEffects<AppAction> {
        switch action {
        case .updateProfile:
            state.profile = action.newProfile
            // 返回需要触发的其他 Action
            return .effect(.settings(.updateUserProfile(action.newProfile)))
        default:
            return .none
        }
    }
}

// 主 Reducer 组合器
struct AppReducer {
    static func reduce(state: inout AppState, action: AppAction) {
        let sideEffects: SideEffects<AppAction>
        
        switch action {
        case .user(let userAction):
            sideEffects = UserReducer.reduce(state: &state.user, action: userAction)
            
        case .settings(let settingsAction):
            sideEffects = SettingsReducer.reduce(state: &state.settings, action: settingsAction)
        }
        
        // 处理 side effects
        handleSideEffects(sideEffects, state: &state)
    }
    
    private static func handleSideEffects(_ sideEffects: SideEffects<AppAction>, state: inout AppState) {
        switch sideEffects {
        case .effect(let action):
            reduce(state: &state, action: action)
        case .effects(let actions):
            actions.forEach { reduce(state: &state, action: $0) }
        case .none:
            break
        }
    }
}

4. 使用 Middleware 进行通信

通过 Middleware 来拦截 Action 并触发其他 Action:

struct CrossModuleMiddleware: Middleware {
    typealias State = AppState
    
    func process(state: State, action: AppAction, dispatch: @escaping (AppAction) -> Void) {
        switch action {
        case .user(.logout):
            // 用户登出时,清理所有相关模块
            dispatch(.settings(.resetToDefaults))
            dispatch(.notifications(.clearAll))
            dispatch(.cart(.clearCart))
            
        case .user(.loginSuccess(let user)):
            // 用户登录成功,初始化相关模块
            dispatch(.settings(.loadUserPreferences(user.id)))
            dispatch(.notifications(.registerDeviceToken))
            
        default:
            break
        }
    }
}

// Middleware 协议
protocol Middleware {
    associatedtype State
    func process(state: State, action: AppAction, dispatch: @escaping (AppAction) -> Void)
}

5. 依赖注入式 Reducer

为 Reducer 提供调用其他 Reducer 的能力:

struct ReducerEnvironment {
    let dispatch: (AppAction) -> Void
    let getState: () -> AppState
}

func userReducer(environment: ReducerEnvironment) -> (inout UserState, UserAction) -> Void {
    return { state, action in
        switch action {
        case .purchaseCompleted(let product):
            state.purchasedProducts.append(product)
            // 通过 environment 调用其他 reducer
            environment.dispatch(.settings(.unlockFeature(product.featureId)))
            environment.dispatch(.analytics(.trackPurchase(product)))
        default:
            break
        }
    }
}

6. 实际应用示例

// 完整的应用示例
class Store: ObservableObject {
    @Published private(set) var state: AppState
    private let reducer: (inout AppState, AppAction) -> Void
    private let middlewares: [Middleware]
    
    init(state: AppState, reducer: @escaping (inout AppState, AppAction) -> Void, middlewares: [Middleware]) {
        self.state = state
        self.reducer = reducer
        self.middlewares = middlewares
    }
    
    func dispatch(_ action: AppAction) {
        // 先执行 middleware
        middlewares.forEach { $0.process(state: state, action: action, dispatch: self.dispatch) }
        
        // 执行 reducer
        reducer(&state, action)
    }
}

// 在 SwiftUI 中使用
struct ContentView: View {
    @EnvironmentObject var store: Store
    
    var body: some View {
        Button("Login") {
            store.dispatch(.user(.login(username: "user", password: "pass")))
            // 这会自动触发相关的 settings 和 notifications actions
        }
    }
}

最佳实践建议

  1. 优先使用 Action 链式调用 - 最符合 Redux 单向数据流原则
  2. 对于复杂副作用使用 Effect 系统 - 更适合异步操作和复杂逻辑
  3. 避免直接函数调用 - 保持 Reducer 的纯净性和可测试性
  4. 使用 Middleware 处理横切关注点 - 如日志、分析、错误处理等

选择哪种方式取决于你的应用复杂度和团队偏好。简单的应用可以使用基本的 Action 链式调用,复杂应用可以考虑实现完整的 Effect 系统。