TCA通用状态懒加载复用方案:如何让BaseState符合CaseReducerState?
为了规避懒加载相关的样板代码,我实现了一套通用状态管理方案,包含BaseState、BaseViewModel以及进度、错误状态的通用实现,同时通过BaseContentStateType协议规范内容状态。核心代码如下:
@ObservableState enum BaseState<Input, BaseContentState> { case progress(BaseProgressState<Input>) case content(BaseContentState) case error(BaseErrorState<Input>) init(input: Input) { self = .progress(BaseProgressState(input: input)) } } @Reducer struct BaseViewModel<Input, ContentInput, BaseContentState: BaseContentStateType, BaseContentAction> where ContentInput == BaseContentState.ContentInput { typealias State = BaseState<Input, BaseContentState> enum Action { case progress(BaseProgressAction<Input, ContentInput>) case content(BaseContentAction) case error(BaseErrorAction<Input>) } } protocol BaseContentStateType { associatedtype ContentInput init(contentInput: ContentInput) } @ObservableState struct BaseProgressState<Input> { let input: Input } enum BaseProgressAction<Input, ContentInput> { case load(Input) case loadSuccess(ContentInput) case loadFailure(Input, Error) } struct BaseProgressViewModel<Input, ContentInput>: Reducer { typealias State = BaseProgressState<Input> typealias Action = BaseProgressAction<Input, ContentInput> var body: some ReducerOf<Self> { EmptyReducer() } } struct BaseProgressView<Input, ContentInput>: View { let store: StoreOf<BaseProgressViewModel<Input, ContentInput>> var body: some View { ProgressView() .onAppear { store.send(.load(store.input)) } } } @ObservableState struct BaseErrorState<Input> { let input: Input let error: Error } enum BaseErrorAction<Input> { case retry(Input) } struct BaseErrorViewModel<Input>: Reducer { typealias State = BaseErrorState<Input> typealias Action = BaseErrorAction<Input> var body: some ReducerOf<Self> { EmptyReducer() } } struct BaseErrorView<Input>: View { let store: StoreOf<BaseErrorViewModel<Input>> var body: some View { Button("Retry") { store.send(.retry(store.input)) } } }
但在编写具体业务的FeatureViewModel和FeatureView时,遇到以下错误:
Instance method 'ifLet(_:action:)' requires that 'FeatureViewModel.State' (aka 'BaseState<FeatureInput, FeatureContentViewModel.State>') conform to 'CaseReducerState'
解决方法
要让BaseState符合CaseReducerState协议,需要满足TCA对该协议的核心要求:状态枚举的每个case对应子Reducer的State,且Action能映射到子Reducer的Action。具体修改如下:
1. 让BaseState遵循CaseReducerState
修改BaseState定义,添加协议一致性,并关联对应的Action类型:
@ObservableState enum BaseState<Input, BaseContentState>: CaseReducerState where BaseContentState: CaseReducerState { case progress(BaseProgressState<Input>) case content(BaseContentState) case error(BaseErrorState<Input>) init(input: Input) { self = .progress(BaseProgressState(input: input)) } // 关联BaseViewModel的Action作为CaseReducerState的Action类型 typealias Action = BaseViewModel<Input, BaseContentState.ContentInput, BaseContentState, BaseContentState.Action>.Action }
2. 调整BaseContentStateType协议
让内容状态也必须遵循CaseReducerState,确保子状态能被TCA的Scope处理:
protocol BaseContentStateType: CaseReducerState { associatedtype ContentInput init(contentInput: ContentInput) }
3. 完善BaseViewModel的Reducer逻辑
在BaseViewModel中添加子Reducer的Scope关联,并处理状态转换逻辑:
@Reducer struct BaseViewModel<Input, ContentInput, BaseContentState: BaseContentStateType, BaseContentAction> where ContentInput == BaseContentState.ContentInput, BaseContentAction == BaseContentState.Action { typealias State = BaseState<Input, BaseContentState> enum Action { case progress(BaseProgressAction<Input, ContentInput>) case content(BaseContentAction) case error(BaseErrorAction<Input>) } var body: some ReducerOf<Self> { // 关联进度状态的子Reducer Scope(state: \.progress, action: /Action.progress) { BaseProgressViewModel<Input, ContentInput>() } // 关联内容状态的子Reducer Scope(state: \.content, action: /Action.content) { BaseContentState.Reducer() // 假设BaseContentState关联了对应的Reducer类型 } // 关联错误状态的子Reducer Scope(state: \.error, action: /Action.error) { BaseErrorViewModel<Input>() } // 处理状态间的转换逻辑 Reduce { state, action in switch action { case .progress(.loadSuccess(let contentInput)): state = .content(BaseContentState(contentInput: contentInput)) return .none case .progress(.loadFailure(let input, let error)): state = .error(BaseErrorState(input: input, error: error)) return .none case .error(.retry(let input)): state = .progress(BaseProgressState(input: input)) return .none default: return .none } } } }
原理说明
CaseReducerState是TCA中专门用于支持Scope、ifLet等状态分解API的协议,它要求枚举类型的每个case对应一个可独立处理的子状态,并且Action能准确映射到子Reducer的Action。通过上述修改,BaseState的每个case都关联了对应的子Reducer,状态转换逻辑也被统一管理,自然就能满足ifLet方法的协议要求。
内容的提问来源于stack exchange,提问作者sergeifabian

