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

TCA通用状态懒加载复用方案:如何让BaseState符合CaseReducerState?

解决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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 14:30:08