😬
[TCA] TCAのTree-based Navigationのコードを理解する
概要
TCAのTree-based Navigationにでてくる以下の要素を理解する。
@Presents
PresentationAction
ifLet(_: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の中のitemDetail
Stateに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