🎯

TCA で struct State の中にある enum を取り扱う方法

2023/01/22に公開3
struct ExampleReducer: ReducerProtocol {
  struct State: Equatable {
    enum ExampleEnum: Equatable {
      case hoge(Hoge.State)
      case fuga(Fuga.State)
    }

    var example: ExampleEnum?
  }
  
  enum Action: Equatable {
    case hoge(Hoge.Action)
    case fuga(Fuga.Action)
  }
  
  // ...
}

上記のように struct State の中で、複数の State から構成されるような enum を保持している場合、Reducer では以下のように取り扱うことができます。(Scope して更に Scope するというイメージ)

var body: some ReducerProtocol<State, Action> {
  Scope(state: \.example, action: /.self) {
    Scope(state: /State.ExampleEnum.hoge, action: /Action.hoge) {
      HogeReducer()
    }
    Scope(state: /State.ExampleEnum.fuga, action: /Action.fuga) {
      FugaReducer()
    }
  }
}

View では例えば以下のように利用できます。

var body: some View {
  IfLetStore(
    store.scope(state: \.example),
    then: { store in
      SwitchStore(store) {
        CaseLet(
	  state: /ExampleReducer.State.ExampleEnum.hoge,
	  action: ExampleReducer.Action.hoge,
	  then: HogeView.init
	)
	CaseLet(
	  state: /ExampleReducer.State.ExampleEnum.fuga,
	  action: ExampleReducer.Action.fuga,
	  then: FugaView.init
	)
      }
    }
  )
}

参考

Discussion

yamachoyamacho

こちら1.7からSwitchStore→switchになると思いますが、こちらは現状でも
var example: ExampleEnum?
のように扱うことはできるのでしょうか?
https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.7#Replacing-SwitchStore-and-CaseLet-with-switch-and-case

アイカワアイカワ

はい!多くの API は不要になっているのですが、現状でも同じように利用できると思います!
ただ、この記事の内容と同じことは enum の State を持つ Reducer を一つ作ればもう少しシンプルにできそうなので、そちらの方が良いかもしれないです 🙏 (コンパイラなしで書いているので誤りあるかもですが ↓ にその例を貼ります)

// Reducer
@Reducer
struct ExampleReducer {
  @ObservableState
  struct State: Equatable {
    var example: ExampleEnum.State?
  }

  enum Action {
    case example(ExampleEnum.Action)
  }

  var body: some ReducerOf<Self> {
    Reduce { state, action in
      // ...
    }
    .ifLet(\.example, action: \.example) {
      ExampleEnum.body
    }
  }

  @Reducer(state: .equatable)
  enum ExampleEnum {
    case hoge(Hoge)
    case fuga(Fuga)
  }
}

// View
var body: some View {
  if let exampleStore = store.scope(
    state: \.example,
    action: \.example
  ) {
    switch exampleStore.state {
    case .hoge:
      if let hogeStore = exampleStore.scope(...) { ... }
    case .fuga:
      if let fugaStore = exampleStore.scope(...) { ... }
    }
  }
}
yamachoyamacho

ご返信ありがとうございます!
昨日いろいろためしていて同じ結論にいたりました!
pointfreeのDestinationでやっている@Reducerの例がExampleEnumで使えそうだなと思っていました。
https://github.com/pointfreeco/swift-composable-architecture/blob/3c1b72ff7cb11a51c2bda9272889e3b24df767f8/Examples/CaseStudies/SwiftUICaseStudies/03-Navigation-Multiple-Destinations.swift#L9-L16
アイカワさんのおかげで確信しました。ありがとうございます!