😬
[TCA] TCAのTree-based Navigationのコードを理解する
概要
TCAのTree-based Navigationにでてくる以下の要素を理解する。
@PresentsPresentationActionifLet(_:action:destination:fileID:filePath:line:column:)
TCAのTree-based Navigationってなんだっけ?
Tree-based Navigationを使用した実装例
"商品のリストを表示するView"(ItemListFeature)と、その画面からDrill-downで遷移する"商品詳細View"(ItemDetailFeature)があったとする。

TCAのTree-based Navigationを利用すると、以下のようなコードになる。
ItemListFeatureReducer.swift
@Reducer
struct ItemListFeature {
@ObservableState
struct State: Equatable {
@Presents var itemDetail: ItemDetailFeature.State?
// ...
}
enum Action {
case itemDetail:(PresentationAction<ItemDetailFeature.Action>)
// ...
}
var body: some ReducerOf<Self> {
Reduce { state, action in
// ...
}
.ifLet(\.$itemDetail, action: \.itemDetail) {
ItemDetailFeature()
}
}
ItemListView.swift
struct ItemListView: View {
@Bindable var store: StoreOf<ItemDetailFeature>
var body: some View {
ListView()
.navigationDestination(item: $store.scope(state: \.itemDetail, action: \.itemDetail) { store in
ItemDetailView(store: store)
}
}
}
コードには、@Presents、PresentationAction、ifLet(, action:)などがでてくる。
@Presents
@ObservableState
struct State: Equatable {
@Presents var itemDetail: ItemDetailFeature.State?
// ...
}
-
PresentationStateとObservableStateを組み合わせて使うためのMacro -
PresentationStateは、PresentされるStateのためのProperty Wrapper - 後述の
ifLetと組み合わせて使用する。
PresentationAction
enum Action {
case itemDetail:(PresentationAction<ItemDetailFeature.Action>)
// ...
}
- PresentされるActionのためのProperty Wrapper
- 後述の
ifLetと組み合わせて使用する。 -
ChildFeatureが表示・非表示されるときに呼ばれる2つのActionを提供する
-
PresentationAction.presented(_:)- ChildFeature内部でActionが発行されたときに呼ばれる。
-
PresentationAction.dismiss- PresentationStateに
nilがセットされたときに呼ばれる。 -
ChildFeatureStateがnilになる前に、ParentFeatureからChildFeatureStateにアクセスできる。
- PresentationStateに
-
PresentationAction.presented(_:), PresentationAction.dismissの使用例
ItemListFeature.swift
// ...
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
//...
case .itemDetail(.presented(.addItem)):
//...
case .itemDetail(.dismiss):
//...
}
}
.ifLet(\.$itemDetail, action: \.itemDetail) {
ItemDetailFeature()
}
}
-
.itemDetail(.presented(.addItem)):は、子ViewであるItemDetailFeatureにおいて、ItemDetailFeature.Actionの.addItemが発行されたときに呼ばれる。 -
.itemDetail(.dismiss):は、親Stateの中のitemDetailStateにnilがセットされると呼ばれる。
ifLet(_:action:destination:fileID:filePath:line:column:)
Reduce { state, action in
}
.ifLet(\.$itemDetail, action: \.itemDetail) {
ItemDetailFeature()
}
- パラメータ
- toPresentationState
- 第一引数でPresentしたいChildFeatureのStateを受け取る
- Stateの型は、PresentationStateへのKeyPathになっている。(ChildStateが
@Presentである必要)
- toPresentationAction
- 第二引数で、PresentしたChildFeatureのActionを受け取る
- Actionの型は、PresentationActionへのCasePathになっている(ChildActionが
PresentationActionである必要)。
- destination
- 第三引数で、PresentしたいChildFeatureのReducerを受け取る
- toPresentationState
- 戻り値
- ParentFeatureのReducerと、PresentしたいChildFeatureのReducerを、組み合わせたReducer
-
ifLetのはたらき-
PresentationAction.dismissのActionが呼ばれたときは、ChildStateをnilにする前にParentFeatureが呼ばれる。(nilになる前に、ChildFeature.StateにParentがアクセスできる) -
PresentationAction.presented(_:)は、ChildFeature → ParentFeatureの順で呼ばれる。 - ChildStateが
nilになったときは、ChildFeatureのEffectを自動でキャンセルする - ChildFeatureから自身を
dismissできるようにする。
-
Envrionment
- TCA v1.13.0
SampleCode

Discussion