基于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
相关产品推荐
相关产品推荐

