|
144 | 144 | ///
|
145 | 145 | /// Binding actions can also be tested in much the same way regular actions are tested. Rather
|
146 | 146 | /// than send a specific action describing how a binding changed, such as
|
147 |
| - /// `displayNameChanged("Blob")`, you will send a ``Reducer/binding(action:)`` action that |
| 147 | + /// `.displayNameChanged("Blob")`, you will send a ``Reducer/binding(action:)`` action that |
148 | 148 | /// describes which key path is being set to what value, such as `.set(\.$displayName, "Blob")`:
|
149 | 149 | ///
|
150 | 150 | /// ```swift
|
|
244 | 244 | }
|
245 | 245 | }
|
246 | 246 |
|
247 |
| - /// An action type that exposes a `binding` case for the purpose of reducing. |
| 247 | + /// An action type that exposes a `binding` case that holds a ``BindingAction``. |
248 | 248 | ///
|
249 | 249 | /// Used in conjunction with ``BindableState`` to safely eliminate the boilerplate typically
|
250 | 250 | /// associated with mutating multiple fields in state.
|
|
334 | 334 | /// Useful in transforming binding actions on view state into binding actions on reducer state
|
335 | 335 | /// when the domain contains ``BindableState`` and ``BindableAction``.
|
336 | 336 | ///
|
337 |
| - /// For example, we can model an app that can bind a number to a stepper and make a network |
338 |
| - /// request to fetch a number fact with the following domain: |
| 337 | + /// For example, we can model an app that can bind an integer count to a stepper and make a |
| 338 | + /// network request to fetch a fact about that integer with the following domain: |
339 | 339 | ///
|
340 | 340 | /// ```swift
|
341 | 341 | /// struct AppState: Equatable {
|
|
373 | 373 | /// The view may want to limit the state and actions it has access to by introducing a
|
374 | 374 | /// view-specific domain that contains only the state and actions the view needs. Not only will
|
375 | 375 | /// this minimize the number of times a view's `body` is computed, it will prevent the view
|
376 |
| - /// from accessing state or sending actions outside its purview. |
| 376 | + /// from accessing state or sending actions outside its purview. We can define it with its own |
| 377 | + /// bindable state and bindable action: |
377 | 378 | ///
|
378 | 379 | /// ```swift
|
379 | 380 | /// extension AppView {
|
380 | 381 | /// struct ViewState: Equatable {
|
381 |
| - /// var count: Int |
| 382 | + /// @BindableState var count: Int |
382 | 383 | /// let fact: String?
|
383 | 384 | /// // no access to any other state on `AppState`, like child domains
|
384 | 385 | /// }
|
|
391 | 392 | /// }
|
392 | 393 | /// ```
|
393 | 394 | ///
|
394 |
| - /// And in order to transform `BindingAction<ViewState>` into `BindingAction<AppState>`, we |
395 |
| - /// need a writable key path from `AppState` to `ViewState`, which we can get by defining a |
396 |
| - /// computed property with a getter and setter, where the setter can communicate any updates to |
397 |
| - /// bindable view state to the store: |
| 395 | + /// In order to transform a `BindingAction<ViewState>` sent from the view domain into a |
| 396 | + /// `BindingAction<AppState>`, we need a writable key path from `AppState` to `ViewState`. We |
| 397 | + /// can synthesize one by defining a computed property on `AppState` with a getter and a setter. |
| 398 | + /// The setter should communicate any mutations to bindable state back to the parent state: |
398 | 399 | ///
|
399 | 400 | /// ```swift
|
400 | 401 | /// extension AppState {
|
|
405 | 406 | /// }
|
406 | 407 | /// ```
|
407 | 408 | ///
|
408 |
| - /// Finally, in the view we can use ``Store/scope(state:action:)-9iai9`` to pluck out view |
409 |
| - /// state, embed view actions, and transform binding actions between domains: |
| 409 | + /// With this property defined it is now possible to transform a `BindingAction<ViewState>` into |
| 410 | + /// a `BindingAction<AppState>`, which means we can transform a `ViewAction` into an |
| 411 | + /// `AppAction`. This is where `pullback` comes into play: we can unwrap the view action's |
| 412 | + /// binding action on view state and transform it with `pullback` to work with app state. We can |
| 413 | + /// define a helper that performs this transformation, as well as route any other view actions |
| 414 | + /// to their reducer equivalents: |
410 | 415 | ///
|
411 | 416 | /// ```swift
|
412 |
| - /// var body: some View { |
413 |
| - /// WithViewStore( |
414 |
| - /// self.store.scope( |
415 |
| - /// state: { .init(count: $0.count, fact: $0.fact) } |
416 |
| - /// action: { |
417 |
| - /// switch $0 { |
418 |
| - /// case let .binding(action): |
419 |
| - /// return .binding(action.pullback(\.view)) // transform binding action |
420 |
| - /// case .factButtonTapped: |
421 |
| - /// return .factButtonTapped |
422 |
| - /// } |
423 |
| - /// } |
424 |
| - /// ) |
425 |
| - /// ) { viewStore in |
426 |
| - /// ... |
| 417 | + /// extension AppAction { |
| 418 | + /// static func view(_ viewAction: AppView.ViewAction) -> Self { |
| 419 | + /// switch viewAction { |
| 420 | + /// case let .binding(action): |
| 421 | + /// // transform view binding actions into app binding actions |
| 422 | + /// return .binding(action.pullback(\.view)) |
| 423 | + /// |
| 424 | + /// case let .factButtonTapped |
| 425 | + /// // route `ViewAction.factButtonTapped` to `AppAction.factButtonTapped` |
| 426 | + /// return .factButtonTapped |
| 427 | + /// } |
427 | 428 | /// }
|
428 | 429 | /// }
|
429 | 430 | /// ```
|
430 | 431 | ///
|
431 |
| - /// This is a lot, though both state and action transformations could be pulled out to their own |
432 |
| - /// helpers. Importantly, the view has whittled away its domain and can only read state it has |
433 |
| - /// access to, and send actions it has access to. |
| 432 | + /// Finally, in the view we can invoke ``Store/scope(state:action:)-9iai9`` with these domain |
| 433 | + /// transformations to leverage the view store's binding helpers: |
| 434 | + /// |
| 435 | + /// ```swift |
| 436 | + /// WithViewStore( |
| 437 | + /// self.store.scope(state: \.view, action: AppAction.view) |
| 438 | + /// ) { viewStore in |
| 439 | + /// Stepper("\(viewStore.count)", viewStore.$count) |
| 440 | + /// Button("Get number fact") { viewStore.send(.factButtonTapped) } |
| 441 | + /// if let fact = viewStore.fact { |
| 442 | + /// Text(fact) |
| 443 | + /// } |
| 444 | + /// } |
| 445 | + /// ``` |
434 | 446 | ///
|
435 | 447 | /// - Parameter keyPath: A key path from a new type of root state to the original root state.
|
436 | 448 | /// - Returns: A binding action over a new type of root state.
|
|
0 commit comments