The Composable Architecture(TCA)メモ

ViewStore

binding

public func binding<LocalState>(
    get: @escaping (State) -> LocalState,
    send localStateToViewAction: @escaping (LocalState) -> Action
) -> Binding<LocalState>

https://github.com/pointfreeco/swift-composable-architecture/blob/main/Sources/ComposableArchitecture/ViewStore.swift#L125-L141

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>

https://github.com/pointfreeco/swift-composable-architecture/blob/main/Sources/ComposableArchitecture/Reducer.swift#L250-L264

子の 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

https://github.com/pointfreeco/swift-composable-architecture/blob/main/Sources/ComposableArchitecture/Reducer.swift#L155-L159

複数の 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
        ...
        }
    }
)