You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于ReducerProtocol的TCA跨Reducer通信与状态更新问题

TCA(ReducerProtocol)跨Reducer状态同步方案

针对你遇到的TodoItemFeature更新后同步状态到TodoListFeature的问题,这是TCA父子Reducer组合的典型场景,以下是具体实现步骤:

1. 调整子Feature(TodoItemFeature)

扩展动作类型,增加保存完成的标记动作,让父Feature能感知到更新结果:

struct TodoItemFeature: ReducerProtocol {
    struct State {
        var currentItem: TodoItem // 存储当前编辑的待办项
        var saveError: Error?
    }

    enum Action {
        case updatedItem(TodoItem)
        case saveCompleted(Result<TodoItem, Error>)
    }

    func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
        switch action {
        case .updatedItem(let item):
            state.currentItem = item
            // 执行异步保存逻辑
            return .task {
                do {
                    try await saveItem(item) // 替换为实际保存代码
                    return .saveCompleted(.success(item))
                } catch {
                    return .saveCompleted(.failure(error))
                }
            }
        case .saveCompleted(let result):
            state.saveError = result.failure ?? nil
            return .none
        }
    }

    // 模拟保存方法,实际项目替换为网络/本地存储逻辑
    private func saveItem(_ item: TodoItem) async throws {}
}

2. 改造父Feature(TodoListFeature)

让父Feature包含子Feature的状态,并通过动作转发、状态映射实现同步:

struct TodoListFeature: ReducerProtocol {
    struct State {
        var todoItems: [TodoItem]
        var editingItem: TodoItemFeature.State? // 子Feature状态
        var editingItemId: UUID? // 定位待更新项的ID(假设TodoItem有UUID类型id字段)
    }

    enum Action {
        case onAppear
        case dataLoaded(Result<[TodoItem], Error>)
        case todoItem(TodoItemFeature.Action) // 转发子Feature动作
        case startEditingItem(TodoItem) // 触发编辑的动作
    }

    func reduce(into state: inout State, action: Action) -> EffectTask<Action> {
        switch action {
        case .onAppear:
            // 异步加载初始数据
            return .task {
                do {
                    let items = try await loadTodoItems()
                    return .dataLoaded(.success(items))
                } catch {
                    return .dataLoaded(.failure(error))
                }
            }
        case .dataLoaded(let result):
            if case .success(let items) = result {
                state.todoItems = items
            }
            return .none
        case .startEditingItem(let item):
            // 初始化子Feature状态,进入编辑流程
            state.editingItem = TodoItemFeature.State(currentItem: item)
            state.editingItemId = item.id
            return .none
        case .todoItem(let childAction):
            // 调用子Reducer处理动作,映射Effect到父Action
            guard var editingState = state.editingItem else { return .none }
            let childEffect = TodoItemFeature().reduce(into: &editingState, action: childAction)
            state.editingItem = editingState
            let mappedEffect = childEffect.map(Action.todoItem)

            // 处理子Feature的保存完成动作,更新父列表状态
            if case .saveCompleted(.success(let updatedItem)) = childAction {
                if let index = state.todoItems.firstIndex(where: { $0.id == updatedItem.id }) {
                    state.todoItems[index] = updatedItem
                }
                state.editingItem = nil
                state.editingItemId = nil
            }

            return mappedEffect
        }
    }

    // 模拟加载数据方法
    private func loadTodoItems() async throws -> [TodoItem] { [] }
}

关键逻辑说明

  • 子动作透传:父Feature通过.todoItem分支接收子Feature的所有动作,保证数据流能从子到父传递。
  • 状态联动更新:当子Feature完成保存并发送.saveCompleted(.success)动作时,父Feature找到列表中对应的待办项并替换,实现状态同步。
  • Effect映射:将子Reducer返回的Effect转换为父Action类型,确保异步操作的动作能正确回到父Reducer处理。

如果你的场景中两个Feature是同级关系而非父子,也可以通过共享Dependency(比如统一的数据仓库)来实现状态同步,但父子Reducer组合是TCA中处理这类编辑-列表同步场景的标准实践,更符合单向数据流的设计原则。

内容的提问来源于stack exchange,提问作者Matt Beaney

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 14:47:04