TCA下ForEachStore嵌套SwitchStore时非关联视图意外重渲染问题
Swift Composable Architecture 定时器触发无关视图全量重渲染问题
问题场景
AppState中通过IdentifiedArray存储RowState行数据,每个RowState包含EnumRowState枚举类型的状态属性AppView通过ForEachStore渲染RowView,在RowView中通过SwitchStore匹配EnumRowState的枚举值,展示对应类型的子视图- 子模块
liveReducer中实现了每秒触发一次的Effect.timer,预期定时器带来的状态更新仅会触发对应的LiveView重渲染
异常表现
- 定时器运行时,无关联状态变更的
AddView会同步随定时器每秒重渲染:在AddView中添加的调试控件Text("\(Date())")观测到日期每秒刷新,但AddState本身并未发生任何关联变更 - 从菜单选择条目后,包括
navBarTitle在内的所有视图都会同步更新 - 为
appReducer添加.debug()调试算子后,日志中rows字段的状态变更标注为...(1 unchanged),符合状态未变更的预期,但每次Effect.timer触发时所有行视图仍会全量重渲染
实现说明
中间层reducer参考Swift论坛公开的枚举属性pullback reducer方案编写。
完整复现代码
struct AppState: Equatable { var rows: IdentifiedArray<UUID, RowState> = [] } enum AppAction: Equatable { case row(id: UUID, action: RowAction) } public struct RowState: Equatable, Identifiable { public var enumRowState: EnumRowState public let id: UUID } public enum EnumRowState: Equatable { case add(AddState) case live(LiveState) } public enum RowAction: Equatable { case live(AddAction) case add(LiveAction) } public struct LiveState: Equatable { public var secondsElapsed = 0 } enum LiveAction: Equatable { case onAppear case onDisappear case timerTicked } struct AddState: Equatable { } enum AddAction: Equatable { } public let liveReducer = Reducer<LiveState, LiveAction, LiveEnvironment>.init({ state, action, environment in switch action { case .onAppear: return Effect .timer( id: state.baby.uid, every: 1, tolerance: .zero, on: environment.mainQueue) .map { _ in LiveAction.timerTicked } case .timerTicked: state.secondsElapsed += 1 return .none case .onDisappear: return .cancel(id: state.baby.uid) } }) public let addReducer = Reducer<AddState, AddAction, AddEnvironment>.init({ state, action, environment in switch action {} }) /// /// 用于拉取枚举状态的中间层reducer,供`SwitchStore`使用 /// public var intermediateAddReducer: Reducer<EnumRowState, RowAction, RowEnvironment> { return addReducer.pullback( state: /EnumRowState.add, action: /RowAction.add, environment: { ... } ) } public var intermediateLiveReducer: Reducer<EnumRowState, RowAction, RowEnvironment > { return liveReducer.pullback( state: /EnumRowState.live, action: /RowAction.live, environment: { ... } ) } public let rowReducer: Reducer<RowState, RowAction, RowEnvironment> = .combine( intermediateAddReducer.pullback( state: \RowState.enumRowState, action: /RowAction.self, environment: { $0 } ), intermediateLiveReducer.pullback( state: \RowState.enumRowState, action: /RowAction.self, environment: { $0 } ) ) let appReducer: Reducer<AppState, AppAction, AppEnvironment> = .combine( rowReducer.forEach( state: \.rows, action: /AppAction.row(id:action:), environment: { ... } ), .init({ state, action, environment in switch action { case AppAction.onAppear: state.rows = [ RowState(id: UUID(), enumRowState: .add(AddState())), RowState(id: UUID(), enumRowState: .live(LiveState())) ] return .none default: return .none } }) ) .debug() public struct AppView: View { let store: Store<AppState, AppAction> public var body: some View { WithViewStore(self.store) { viewStore in List { ForEachStore( self.store.scope( state: \AppState.rows, action: AppAction.row(id:action:)) ) { rowViewStore in RowView(store: rowViewStore) } } } } public struct RowView: View { public let store: Store<RowState, RowAction> public var body: some View { WithViewStore(self.store) { viewStore in SwitchStore(self.store.scope(state: \RowState.enumRowState)) { CaseLet(state: /EnumRowState.add, action: RowAction.add) { store in AddView(store: store) } CaseLet(state: /EnumRowState.live, action: RowAction.live) { store in LiveView(store: store) } } } } } struct LiveView: View { let store: Store<LiveState, LiveAction> var body: some View { WithViewStore(self.store) { viewStore in Text("\(viewStore.secondsElapsed)") } } } struct AddView: View { let store: Store<AddState, AddAction> var body: some View { WithViewStore(self.store) { viewStore in // 每次liveReducer的secondsElapsed状态变更时,这里都会触发重渲染!? Text("\(Date())") } } }
内容的提问来源于stack exchange,提问作者cohen72
相关产品推荐
相关产品推荐

