The Composable Architecture(TCA)メモ
ViewStore
binding
public func binding<LocalState>( get: @escaping (State) -> LocalState, send localStateToViewAction: @escaping (LocalState) -> Action ) -> Binding<LocalState>
ViewStore の State, Action から Binding を作る。
双方方向バインディングをしたい時に使う。
Binding の getter では state の値を返し、setter では渡されたアクションを send する。
// 例 struct State { var name = "" } enum Action { case nameChanged(String) } TextField( "名前を入力してください", text: viewStore.binding( get: { $0.name }, send: { Action.nameChanged($0) } ) )
Reducer
pullback
public func pullback<GlobalState, GlobalAction, GlobalEnvironment>( state toLocalState: WritableKeyPath<GlobalState, State>, action toLocalAction: CasePath<GlobalAction, Action>, environment toLocalEnvironment: @escaping (GlobalEnvironment) -> Environment ) -> Reducer<GlobalState, GlobalAction, GlobalEnvironment>
子の Reducer を親の State, Action に紐づいた Reducer へ変換する。
これにより、単一の Reducer が大きくなりすぎてしまわないように適切に子 Reducer へと分割し、それらをマージして親 Reducer を作ることができる。
// 例 // 親の State struct AppState { var settings: SettingsState, ... } // 親の Action enum AppAction { case settings(SettingsAction), ... } // 子の Reducer let settingsReducer = Reducer<SettingsState, SettingsAction, SettingsEnvironment> { ... } // 親の Reducer // combine で複数の子 Reducer を結合する let appReducer = Reducer<AppState, AppAction, AppEnviroment> = .combine( settingsReducer.pullback( state: \.settings, action: /AppAction.settings, environment: { $0.settings } ), // 他の子 Reducer ... )
combine
public static func combine(_ reducers: [Reducer]) -> Reducer
複数の Reducer を順番に実行する単一の Reducer を作る。
順番に実行するため配列の記述順序に影響し、親・子関係の Reducer が同一の State, Action を使用する際に問題を生じる場合がある。
特に Optional な Reducer を用いる場合に起こりやすく、子 Reducer の前に 親 Reducer を実行すると assertion failure となる可能性があるため、子から親という順序にするのが一般的である。
// 例 let parentReducer = Reducer<ParentState, ParentAction, ParentEnvironment>.combine( // 後続の Reducer で state を nil にしているが先に実行されるので state を処理できる childReducer.optional().pullback( state. \.child, action: /ParentAction.child, ennvironment: { $0.child } ), // childReducer の後に実行されるので state を nil にしても childReducer には影響がない Reducer { state, action, environment in switch action { case .child(.dismiss): state.child = nil return .none ... } } )