🍄

マクロが実装されたTCAは導入しやすいかも?

2023/12/15に公開

TCAの導入で気になるところ

基本的に自分の勉強不足と、好みの部分があります。

  • WithViewStoreがよくわからない。
  • しかもbody: some ViewのTopにいる。
  • WithViewStoreがよくわからないので、Viewのライフサイクルが複雑そうに見える

マクロ導入後のTCA

@Reducer, @ObservableStateというマクロが導入され、View部分をスッキリ宣言することができるようになります。

Swift 5.9以前のTCA

import SwiftUI
import ComposableArchitecture

struct CounterFeature: Reducer {
  struct State: Equatable {
    var count = 0
  }

  enum Action {
    case decrementButtonTapped
    case incrementButtonTapped
  }

  func reduce(into state: inout State, action: Action) -> Effect<Action> {
    switch action {
    case .decrementButtonTapped:
      state.count -= 1
      return .none
    case .incrementButtonTapped:
      state.count += 1
      return .none
    }
  }
}

struct CounterView: View {
  let store: StoreOf<CounterFeature>

  var body: some View {
    WithViewStore(self.store, observe: { $0 }) { viewStore in
      VStack {
        Text("\(viewStore.count)")
          .padding()
          .background(Color.yellow, in: .rect(cornerRadius: 10))

        HStack {
          Button("-") {
            viewStore.send(.decrementButtonTapped)
          }
          .padding()
          .background(Color.green, in: .rect(cornerRadius: 10))

          Button("+") {
            viewStore.send(.incrementButtonTapped)
          }
          .padding()
          .background(Color.red, in: .rect(cornerRadius: 10))
        }
      }
      .font(.largeTitle)
    }
  }
}

Swift 5.9からのTCA

import SwiftUI
import ComposableArchitecture

@Reducer
struct CounterFeature {
  @ObservableState
  struct State: Equatable {
    var count = 0
  }

  enum Action {
    case decrementButtonTapped
    case incrementButtonTapped
  }

  var body: some Reducer<State, Action> {
    Reduce { state, action in
      switch action {
      case .decrementButtonTapped:
        state.count -= 1
        return .none
      case .incrementButtonTapped:
        state.count += 1
        return .none
      }
    }
  }
}

struct CounterView: View {
  let store: StoreOf<CounterFeature>

  var body: some View {
    VStack {
      Text("\(store.count)")
        .padding()
        .background(Color.yellow, in: .rect(cornerRadius: 10))

      HStack {
        Button("-") {
          store.send(.decrementButtonTapped)
        }
        .padding()
        .background(Color.green, in: .rect(cornerRadius: 10))

        Button("+") {
          store.send(.incrementButtonTapped)
        }
        .padding()
        .background(Color.red, in: .rect(cornerRadius: 10))
      }
    }
    .font(.largeTitle)
  }
}

変更点

Viewの本体(body)が標準の@Stateとほぼ同じフォーマットになりました。
これによりStateとActionを分離するだけの綺麗なアーキテクチャに見えるようになりました。

PointFree

PointFreeのライブラリはTCAが大きなライブラリですが、そもそもSwiftUIを使わない場面でも便利なライブラリが多いので、是非見てみてください。

https://zenn.dev/zunda_pixel/articles/db3cf165e13fce

https://zenn.dev/zunda_pixel/articles/f35ba56d04a105

Discussion