@@ -40,16 +40,120 @@ public final class Store<State, Action> {
40
40
/// struct AppAction { case login(LoginAction), ... }
41
41
///
42
42
/// // A store that runs the entire application.
43
- /// let store = Store(initialState: AppState(), reducer: appReducer, environment: ())
43
+ /// let store = Store(
44
+ /// initialState: AppState(),
45
+ /// reducer: appReducer,
46
+ /// environment: AppEnvironment()
47
+ /// )
44
48
///
45
49
/// // Construct a login view by scoping the store to one that works with only login domain.
46
- /// let loginView = LoginView(
50
+ /// LoginView(
47
51
/// store: store.scope(
48
52
/// state: { $0.login },
49
53
/// action: { AppAction.login($0) }
50
54
/// )
51
55
/// )
52
56
///
57
+ /// Scoping in this fashion allows you to better modularize your application. In this case,
58
+ /// `LoginView` could be extracted to a module that has no access to `AppState` or `AppAction`.
59
+ ///
60
+ /// Scoping also gives a view the opportunity to focus on just the state and actions it cares
61
+ /// about, even if its feature domain is larger.
62
+ ///
63
+ /// For example, the above login domain could model a two screen login flow: a login form followed
64
+ /// by a two-factor authentication screen. The second screen's domain might be nested in the
65
+ /// first:
66
+ ///
67
+ /// struct LoginState: Equatable {
68
+ /// var email = ""
69
+ /// var password = ""
70
+ /// var twoFactorAuth: TwoFactorAuthState?
71
+ /// }
72
+ ///
73
+ /// enum LoginAction: Equatable {
74
+ /// case emailChanged(String)
75
+ /// case loginButtonTapped
76
+ /// case loginResponse(Result<TwoFactorAuthState, LoginError>)
77
+ /// case passwordChanged(String)
78
+ /// case twoFactorAuth(TwoFactorAuthAction)
79
+ /// }
80
+ ///
81
+ /// The login view holds onto a store of this domain:
82
+ ///
83
+ /// struct LoginView: View {
84
+ /// let store: Store<LoginState, LoginAction>
85
+ ///
86
+ /// var body: some View { ... }
87
+ /// }
88
+ ///
89
+ /// If its body were to use a view store of the same domain, this would introduce a number of
90
+ /// problems:
91
+ ///
92
+ /// * The login view would be able to read from `twoFactorAuth` state. This state is only intended
93
+ /// to be read from the two-factor auth screen.
94
+ ///
95
+ /// * Even worse, changes to `twoFactorAuth` state would now cause SwiftUI to recompute
96
+ /// `LoginView`'s body unnecessarily.
97
+ ///
98
+ /// * The login view would be able to send `twoFactorAuth` actions. These actions are only
99
+ /// intended to be sent from the two-factor auth screen (and reducer).
100
+ ///
101
+ /// * The login view would be able to send non user-facing login actions, like `loginResponse`.
102
+ /// These actions are only intended to be used in the login reducer to feed the results of
103
+ /// effects back into the store.
104
+ ///
105
+ /// To avoid these issues, one can introduce a view-specific domain that slices off the subset of
106
+ /// state and actions that a view cares about:
107
+ ///
108
+ /// extension LoginView {
109
+ /// struct State: Equatable {
110
+ /// var email: String
111
+ /// var password: String
112
+ /// }
113
+ ///
114
+ /// enum Action: Equatable {
115
+ /// case emailChanged(String)
116
+ /// case loginButtonTapped
117
+ /// case passwordChanged(String)
118
+ /// }
119
+ /// }
120
+ ///
121
+ /// One can also introduce a couple helpers that transform feature state into view state and
122
+ /// transform view actions into feature actions.
123
+ ///
124
+ /// extension LoginState {
125
+ /// var view: LoginView.State {
126
+ /// .init(email: self.email, password: self.password)
127
+ /// }
128
+ /// }
129
+ ///
130
+ /// extension LoginView.Action {
131
+ /// var feature: LoginAction {
132
+ /// switch self {
133
+ /// case let .emailChanged(email)
134
+ /// return .emailChanged(email)
135
+ /// case .loginButtonTapped:
136
+ /// return .loginButtonTapped
137
+ /// case let .passwordChanged(password)
138
+ /// return .passwordChanged(password)
139
+ /// }
140
+ /// }
141
+ /// }
142
+ ///
143
+ /// With these helpers defined, `LoginView` can now scope its store's feature domain into its view
144
+ /// domain:
145
+ ///
146
+ /// var body: some View {
147
+ /// WithViewStore(
148
+ /// self.store.scope(state: { $0.view }, action: { $0.feature })
149
+ /// ) { viewStore in
150
+ /// ...
151
+ /// }
152
+ /// }
153
+ ///
154
+ /// This view store is now incapable of reading any state but view state (and will not recompute
155
+ /// when non-view state changes), and is incapable of sending any actions but view actions.
156
+ ///
53
157
/// - Parameters:
54
158
/// - toLocalState: A function that transforms `State` into `LocalState`.
55
159
/// - fromLocalAction: A function that transforms `LocalAction` into `Action`.
0 commit comments