You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+30-26Lines changed: 30 additions & 26 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,9 +4,9 @@ A simple Elm-like Store for SwiftUI, based on [ObservableObject](https://develop
4
4
5
5
ObservableStore helps you craft more reliable apps by centralizing all of your application state into one place, and giving you a deterministic system for managing state changes and side-effects. All state updates happen through actions passed to an update function. This guarantees your application will produce exactly the same state, given the same actions in the same order. If you’ve ever used [Elm](https://guide.elm-lang.org/architecture/) or [Redux](https://redux.js.org/), you get the gist.
6
6
7
-
Because `Store` is an [ObservableObject](https://developer.apple.com/documentation/combine/observableobject), and can be used anywhere in SwiftUI that ObservableObject would be used.
7
+
Because `Store` is an [ObservableObject](https://developer.apple.com/documentation/combine/observableobject), it can be used anywhere in SwiftUI that ObservableObject would be used.
8
8
9
-
You can centralize all application state in a single Store, use the Store as an [`EnvironmentObject`](https://developer.apple.com/documentation/swiftui/environmentobject), or create multiple `@StateObject` stores. You can also pass scoped parts of a store down to sub-views as `@Bindings` or as ordinary bare properties of `store.state`.
9
+
You can centralize all application state in a single Store, use the Store as an [`EnvironmentObject`](https://developer.apple.com/documentation/swiftui/environmentobject), or create multiple `@StateObject` stores. You can also pass scoped parts of a store down to sub-views as `@Bindings`, as scoped `ViewStores`, or as ordinary bare properties of `store.state`.
10
10
11
11
## Example
12
12
@@ -187,14 +187,13 @@ Button("Set color to red") {
187
187
188
188
## Bindings
189
189
190
-
`Binding(get:send:tag:)` lets you create a [binding](https://developer.apple.com/documentation/swiftui/binding) that represents some part of the store state. The `get` closure reads the state into a value, and the `tag` closure wraps the value set on the binding in an action. The result is a binding that can be passed to any vanilla SwiftUI view, but changes state only through deterministic updates.
190
+
`StoreProtocol.binding(get:tag:)` lets you create a [binding](https://developer.apple.com/documentation/swiftui/binding) that represents some part of a store state. The `get` closure reads the state into a value, and the `tag` closure wraps the value set on the binding in an action. The result is a binding that can be passed to any vanilla SwiftUI view, but changes state only through deterministic updates.
191
191
192
192
```swift
193
193
TextField(
194
194
"Username"
195
-
text:Binding(
196
-
get: { store.state.username },
197
-
send: store.send,
195
+
text: store.binding(
196
+
get: { state in state.username },
198
197
tag: { username in .setUsername(username) }
199
198
)
200
199
)
@@ -205,9 +204,9 @@ Bottom line, because Store is just an ordinary [ObservableObject](https://develo
205
204
206
205
## Creating scoped child components
207
206
208
-
We can also create component-scoped state and send callbacks from a shared root store. This allows you to create apps from free-standing components that all have their own local state, actions, and update functions, but share the same underlying root store.
207
+
We can also create `ViewStore`s that represent just a scoped part of the root store. You can think of them as being like a binding, but they expose a `StoreProtocol` interface, instead of a binding interface. This allows you to create apps from free-standing components that all have their own local state, actions, and update functions, but share the same underlying root store.
209
208
210
-
Imagine we have a vanilla SWiftUI child view that looks something like this:
209
+
Imagine we have a SWiftUI child view that looks something like this:
Let's integrate this child component with a parent component. This is where `CursorProtocol` comes in. It defines three things:
250
+
To integrate this child component with a parent component, we're going to need 3 functions:
253
251
254
-
- A way to `get` a local state from the root state
255
-
- A way to `set` a local state on a root state
256
-
- A way to `tag` a local action so it becomes a root action
252
+
- A function to `get` a local state from the root state
253
+
- A function to `set` a local state on a root state
254
+
- A function to `tag` a local action so it becomes a root action
257
255
258
-
Together, these functions give us everything we need to map from child to parent.
256
+
Together, these functions give us everything we need to map from child domain to a parent domain. Let's define them as static functions so we have them all in one place.
Let's start by integrating the child view with the parent view. We pass down part of the parent state to the child, along with a scoped `send` callback that will map child actions to parent actions. We can create this scoped `send` with `Address.forward`, passing it the store's send method, and the cursor's tag function.
282
+
Ok, now that we have everything we need to map from the parent domain to the child domain, let's integrate the child view with the parent view.
283
+
284
+
We call the `store.viewStore(get:tag:)` method to create a scoped ViewStore from our store, and pass it the appropriate cursor functions.
285
285
286
286
```swift
287
287
structContentView: View {
288
288
@StateObjectprivatevar store: Store<AppModel>
289
289
290
290
var body: some View {
291
291
ChildView(
292
-
state: store.state.child,
293
-
send: Address.forward(
294
-
send: store.send,
292
+
store: store.viewStore(
293
+
get: AppChildCursor.get,
295
294
tag: AppChildCursor.tag
296
295
)
297
296
)
298
297
}
299
298
}
300
299
```
301
300
302
-
Next, we want to integrate the child's update function into the parent update function. Luckily, `CursorProtocol` synthesizes an `update` function that automatically maps child state and actions to parent state and actions.
301
+
Note that `.viewStore(get:tag:)` is an extension of `StoreProtocol`, so you can call it on `Store` or `ViewStore` to create arbitrarily nested components!
302
+
303
+
Next, we want to integrate the child's update function into the parent update function. Luckily, `ModelProtocol` synthesizes an `update(get:set:tag:state:action:environment)` function that automatically maps child state and actions to parent state and actions.
This tagging/update pattern also gives parent components an opportunity to intercept and handle child actions in special ways.
333
+
And that's it! We have successfully created an isolated child component and integrated it into a parent component. This tagging/update pattern also gives parent components an opportunity to intercept and handle child actions in special ways.
0 commit comments