TCA で View を分割する方法

2 min read読了の目安(約2200字

SwiftUI で View を作成していると、View のコードが大きくなってきたりするので View を分割したいと思うことは少なくないと思います。
しかし、TCA では View において Store を基本的に保持する構成になっており、View に WithViewStore なども絡んでくるため、普通に分割することができません。
この記事では、あまり良くないパターンも含めた2パターンについて説明しようと思います。

  • ViewStore を function 経由で引き渡す方法(TCA ではこれが分割方法として良さそう)
  • Store を引き渡す分割方法(あまり良くない)

ViewStore を function 経由で引き渡す方法

Point-Free さんが公開されている公式の Example でもこの方法で実装していたため、おそらく TCA 的にはこれが一番良さそうな方法かなと思っています。

https://github.com/pointfreeco/swift-composable-architecture/blob/f8608c7421183d93ca246aae1e8656fb42b117f5/Examples/TicTacToe/Sources/Views-SwiftUI/GameSwiftView.swift#L63-L81

例えば以下のような ParentView があった時、

struct ParentView: View {
  let store: Store<ExampleState, ExampleAction>

  var body: some View {
    WithViewStore(store) { viewStore in
      VStack {
        Text("Example Text")
	// この HStack を分割したい
	HStack {
	  Text("この HStack が分割されます")
	}
      }
    }
  }
}

以下のように function の引数として viewStore を指定することによって、View を分割することができます。

struct ParentView: View {
  let store: Store<ExampleState, ExampleAction>
  
  var body: some View {
    WithViewStore(store) { viewStore in
      VStack {
        Text("Example Text")
	
	childView(viewStore: viewStore)
      }
    }
  }
  
  func childView(
    viewStore: Store<ExampleState, ExampleAction>
  ) -> some View {
    HStack {
      Text("この HStack が分割されます")
    }
  }
}

Store を引き渡す分割方法

ParentView については引き続き同様のものとします。

Store を引き渡す分割方法は SwiftUI で普通に View を分割しようとした時と同じような方法になります。具体的には以下のように分割することができます。

struct ParentView: View {
  let store: Store<ExmapleState, ExampleAction>
  
  var body: some View {
    WithViewStore(store) { viewStore in
      VStack {
        Text("Example Text")
	
	ChildView(store: store)
      }
    }
  }
}

struct ChildView: View {
  let store: Store<ExampleState, ExampleAction>
  
  var body: some View {
    WithViewStore(store) { viewStore in
      	HStack {
	  Text("この HStack が分割されます")
	}
    }
  }
}

簡単ではありますが、store をそのまま子 View に渡しているだけです。
自分は最近までこの方法で実装してしまっていましたが、子 View に毎回 store を渡すのは冗長であることに加え、WithViewStore が必要になり、子 View に不要なネストを強いてしまうことも微妙です。

おわりに

単純な話でしたが、意外とわからなくなったりするので記事として残しておきました。
ちょうど今 TCA で適当なアプリを作っているところなので、得られた知見などあればどんどん記事にしていこうと思います。