🏄
TCA v1.5.4のパフォーマンスに親が子を呼び出すドキュメント追加されていた
はじめに
TCAのv1.5.4でパフォーマンスに関するドキュメントに追記されてたことが興味深かった。
要約
- 前提
- Actionを共有する場合にReducerのActionを呼び出すと無駄がある
- どうするか
- Actionでなく関数にして共有する
- 応用
- 配列でもいける
実例
Before/Afterで説明する。
Before
// Handling action from parent feature:
case .buttonTapped:
// Send action to child to perform logic:
return .send(.child(.refresh))
親子のReducerが合成されていて、親が子のReducer経由でActionを実行している。
これを次のように改善している。
After
case .buttonTapped:
return Child().reduce(into: &state.child, action: .refresh)
.map(Action.child))
Childをインスタンス化し、reduce(into:, action:)
メソッドで呼び出している。
これで合成されていない子Reducerを呼び出せる。
mapしているのは、親(呼び出し元)のEffectに変換している。そうしないと子ReducerのEffectになっているためだろう。
何がしたいのか
そもそもSharing logic with actionsのドキュメントから
- ReducerでActionを送信するコストは比較的にある
- Actionを送信すると複数のレイヤーを通過しReducerがActionを解釈する
- 例ではReducerで共通化処理をしているActionをやめ、RedcuerのActionを介さずに単純にメソッドとして共通の処理を関数化している
- Actionを送信すると複数のレイヤーを通過しReducerがActionを解釈する
1つのReducer同士で処理が共通化している場合の実例がある。
実例
@Reducer
struct Feature {
struct State { /* ... */ }
enum Action { /* ... */ }
var body: some Reducer<State, Action> {
Reduce { state, action in
switch action {
case .buttonTapped:
state.count += 1
return self.sharedComputation(state: &state)
case .toggleChanged:
state.isEnabled.toggle()
return self.sharedComputation(state: &state)
case let .textFieldChanged(text):
state.description = text
return self.sharedComputation(state: &state)
}
}
}
func sharedComputation(state: inout State) -> Effect<Action> {
// Some shared work to compute something.
return .run { send in
// A shared effect to compute something
}
}
}
応用を想像
子が配列の場合
子がIdentifiedArrayOfの場合ややこしくなるが、基本はmapする際に引数として子のActionが来るので親である自分のActionを返す。つまり(子のAction) -> (親のAction)
のクロージャを渡せばいい。
public struct ContainerFeature {
public struct State: Equatable {
var array: IdentifiedArrayOf<WeekFeature.State>
...
}
public enum Action: ViewAction, BindableAction {
case array(IdentifiedActionOf<WeekFeature>)
...
}
public var body: some Reducer<State, Action> {
switch action {
case なにか:
let id = ...
return WeekFeature().sharedComputation(
state: &(state.array[id: id]!)
)
.map { (action: WeekFeature) in
Action.array(.element(id: id, action: action))
}
}
}
整理
- 1つのReducer同士で処理を共有化したい
- ActionとしてReducerに定義するのではなく関数化する
- 親と子で処理を共有化したい
- 子をインスタンス化して合成してない状態でReducerのActionを呼び出す
- 応用を想像: 1つのReducer同士で処理を共有下した処理を親が配列の子を呼び出したい
- 子へidでアクセスし、Actionも引数とする
Discussion