@@ -74,18 +74,102 @@ This means `AppView` does not actually need to observe any state changes. This v
74
74
created a single time, whereas if we observed the store then it would re-compute every time a single
75
75
thing changed in either the activity, search or profile child features.
76
76
77
- If sometime in the future we do actually need some state from the store, we can create a localized
78
- "view state" struct that holds only the bare essentials of state that the view needs to do its
79
- job. For example, suppose the activity state holds an integer that represents the number of
80
- unread activities. Then we could observe changes to only that piece of state like so:
77
+ If sometime in the future we do actually need some state from the store, we can start to observe
78
+ only the bare essentials of state necessary for the view to do its job. For example, suppose that
79
+ we need access to the currently selected tab in state:
80
+
81
+ ``` swift
82
+ struct AppState {
83
+ var activity: ActivityState
84
+ var search: SearchState
85
+ var profile: ProfileState
86
+ var selectedTab: Tab
87
+ enum Tab { case activity , search , profile }
88
+ }
89
+ ```
90
+
91
+ Then we can observe this state so that we can construct a binding to ` selectedTab ` for the tab view:
92
+
93
+ ``` swift
94
+ struct AppView : View {
95
+ let store: Store<AppState, AppAction>
96
+
97
+ var body: some View {
98
+ WithViewStore (self .store , observe : { $0 }) { viewStore in
99
+ TabView (selection : viewStore.binding (send : AppAction.tabSelected ) {
100
+ ActivityView (
101
+ store : self .store .scope (state : \.activity , action : AppAction.activity )
102
+ )
103
+ .tag (AppState.Tab .activity )
104
+ SearchView (
105
+ store : self .store .scope (state : \.search , action : AppAction.search )
106
+ )
107
+ .tag (AppState.Tab .search )
108
+ ProfileView (
109
+ store : self .store .scope (state : \.profile , action : AppAction.profile )
110
+ )
111
+ .tag (AppState.Tab .profile )
112
+ }
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ However, this style of state observation is terribly inefficient since _every_ change to `AppState`
119
+ will cause the view to re- compute even though the only piece of state we actually care about is
120
+ the `selectedTab`. The reason we are observing too much state is because we use `observe : { $0 }`
121
+ in the construction of the ``WithViewStore``, which means the view store will observe all of state.
122
+
123
+ To chisel away at the observed state you can provide a closure for that argument that plucks out
124
+ the state the view needs. In this case the view only needs a single field:
125
+
126
+ ```swift
127
+ WithViewStore (self .store , observe : \.selectedTab ) { viewStore in
128
+ TabView (selection : viewStore.binding (send : AppAction.tabSelected ) {
129
+ // ...
130
+ }
131
+ }
132
+ ```
133
+
134
+ In the future, the view may need access to more state. For example, suppose `ActivityState` holds
135
+ onto an `unreadCount` integer to represent how many new activities you have. There's no need to
136
+ observe _all_ of `ActivityState` to get access to this one field. You can observe just the one
137
+ field.
138
+
139
+ Technically you can do this by mapping your state into a tuple, but because tuples are not
140
+ `Equatable ` you will need to provide an explicit `removeDuplicates` argument:
141
+
142
+ ```swift
143
+ WithViewStore (
144
+ self .store ,
145
+ observe : { (selectedTab : $0 .selectedTab , unreadActivityCount : $0 .activity .unreadCount ) },
146
+ removeDuplicates : ==
147
+ ) { viewStore in
148
+ TabView (selection : viewStore.binding (\.selectedTab , send : AppAction.tabSelected ) {
149
+ ActivityView (
150
+ store : self .store .scope (state : \.activity , action : AppAction.activity )
151
+ )
152
+ .tag (AppState.Tab .activity )
153
+ .badge (" \( viewStore.state ) " )
154
+
155
+ // ...
156
+ }
157
+ }
158
+ ```
159
+
160
+ Alternatively, and recommended, you can introduce a lightweight, equatable `ViewState` struct
161
+ nested inside your view whose purpose is to transform the `Store`'s full state into the bare
162
+ essentials of what the view needs:
81
163
82
164
```swift
83
165
struct AppView: View {
84
166
let store: Store<AppState, AppAction>
85
167
86
- struct ViewState {
168
+ struct ViewState : Equatable {
169
+ let selectedTab: AppState.Tab
87
170
let unreadActivityCount: Int
88
171
init (state : AppState) {
172
+ self .selectedTab = state.selectedTab
89
173
self .unreadActivityCount = state.activity .unreadCount
90
174
}
91
175
}
@@ -106,15 +190,16 @@ struct AppView: View {
106
190
}
107
191
```
108
192
109
- Now the `AppView` will re- compute its body only when `activity.unreadCount ` changes. In particular,
110
- no changes to the search or profile features will cause the view to re- compute, and that greatly
111
- reduces how often the view must re- compute.
193
+ This gives you maximum flexibilty in the future for adding new fields to `ViewState` without making
194
+ your view convoluated.
112
195
113
196
This technique for reducing view re- computations is most effective towards the root of your app
114
197
hierarchy and least effective towards the leaf nodes of your app. Root features tend to hold lots
115
198
of state that its view does not need, such as child features, and leaf features tend to only hold
116
199
what's necessary. If you are going to employ this technique you will get the most benefit by
117
- applying it to views closer to the root.
200
+ applying it to views closer to the root. At leaf features and views that need access to most
201
+ of the state, it is fine to continue using `observe: { $0 }` to observe all of the state in the
202
+ store.
118
203
119
204
### CPU intensive calculations
120
205
0 commit comments