Replies: 6 comments 8 replies
-
Hi @GreatApe, can you share a branch with your explorations so that people can play around with it and understand its implications? |
Beta Was this translation helpful? Give feedback.
-
Interesting idea. Small question: how does the view detect that the store changed? If you store the store as an |
Beta Was this translation helpful? Give feedback.
-
@GreatApe I think getting a branch with a full, working example will be helpful, and will give us, and anyone watching this discussion, an opportunity to understand any trade-offs that come with the changes, especially if you convert the case studies that come with the repo. Brandon and I have collectively spent many days trying to come up with something better than the current I'll reserve comments till you have a working implementation, since you may come to similar conclusions in the process. One question about your current sketch, though: what is |
Beta Was this translation helpful? Give feedback.
-
So the way it does it is by listening to its internal As to the ergonomics, you are right that as it is it can only be used like a ViewStore property, it doesn't replace // No stateful views in the body
struct ViewA: View {
var body: some View {
Text("Hello world")
}
}
// All stateful views are system views - we only need a "ViewStore"
struct ViewB: View {
struct ViewState {
let title: String
let sutitle: String
}
let store: Store<B.State, B.Action, ViewState>
init(store: Store<B.State, B.Action, Void>) {
self.store = store.observe(\.viewState)
}
var body: some View {
Text(store.title)
.onTapGesture {
store.send(.tappedTitle)
}
Text(store.subtitle)
}
}
// All stateful views are custom views - we only need a "Store"
struct ViewC: View {
let store: Store<B.State, B.Action, Void>
var body: some View {
VStack {
Text("Children:")
Child1(store.scope(state: \.child1, action: C.Action.child1))
Child2(store.scope(state: \.child2, action: C.Action.child2))
}
}
}
// Stateful views are mix of system views and custom views - we need both a "Store" and a "ViewStore"
struct ViewD: View {
struct ViewState {
let title: String
let subtitle: String
let tabItem
}
let store: Store<B.State, B.Action, ViewState>
init(store: Store<B.State, B.Action, Void>) {
self.store = store.observe(\.viewState)
}
var body: some View {
VStack {
Text(store.title)
Text(store.subtitle)
TabView(selection: store.binding(\.tabItem, send: .tabItemChanged) {
Child1(store.scope(state: \.child1, action: D.Action.child1))
Child2(store.scope(state: \.child2, action: D.Action.child2))
}
}
}
}
} Now it could of course be that I'm missing some situation here, and it's also possible that some people have strong preference for using the framework in a way that requires something like |
Beta Was this translation helpful? Give feedback.
-
I hacked together a proof of concept (internally using ViewStore), and while the ergonomics seem to cover at least all the usage in my own app, I am not sure it is much better than status quo. Yes, it only has one type of store, but separating what you scope to and what you observe will probably cause at least the same amount of confusion - it's not a clear improvement. One option that might be an improvement is to replace struct ViewB: View {
let store: Store<B.State, B.Action>
var body: some View {
store.observing(\.viewState) { viewState, send in
Text(viewState.title)
.onTapGesture {
send(.tappedTitle)
}
Text(viewState.subtitle)
}
}
} |
Beta Was this translation helpful? Give feedback.
-
If Just noting my observations here. It seems this design of |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Instead of hijacking another thread, again, I will post my idea here. In short, the idea is to separate observing from scoping. In the current
WithViewStore
theobserve
option also scopes the state at the same time, which means that it can only really be used in a closure - we wouldn't want to scope the whole store, then we wouldn't be able to peel off other parts and pass down to child views.But what if we make it possible to specify what a store observes, without causing any scoping. This also seems to make it possible to ditch view stores altogether. Please have a look and let me know all the considerations I've missed! I am new to this and don't really know the first principles behind TCA.
No more ViewStores
The idea is to ditch
ViewStores
, and only haveStore
everywhere, but by default aStore
doesn't observe anything, it just holds onto the state and forwards it.In short, you need to explicitly tell it what to observe.
Separating .scope and .observe
So observing doesn't cause any scoping change, but you can of course still scope:
Scoping should probably reset it so that nothing is observed.
Naming
You could imagine calling this store type
ObservingStore
and then doYou could also probably make it so that the store only exposes a
viewState
property, so that you can't even read state that isn't observed.Discussion
Speaking of this, what is the fundamental reason that we want two types, Stores and ViewStores? Are there internal technical reasons or is it usage ergonomics? The way I see it we have four basic requirements:
We use a Store for the first point, and a ViewStore for the second and third. Then we use
Store.scope
for the fourth:I don't know if that is how you see it, but to me it makes sense. And if that's true, then I feel that it could be achieved without viewStores, at the price of adding one more generic type to Store. (Although we could make a type alias for "non-observing store").
Ergonomically, it seems like a big advantage that we only have to deal with a single thing, the store. On the flip side, it seems that we will need to always have an init, where we can tell the store what to observe. We could of course do that when passing the store in to MainView, but that seems like leakage.
Another ergonomic difference is that
scope
andobserve
are now separate. To me it makes sense and doesn't seem harder to understand, but maybe it is for some people.I would love to hear if there are other considerations behind the use of ViewStores in addition to Stores. For example wanting to hide
.send
. I can't see the advantage of that though. And maybe there are other reasons?Beta Was this translation helpful? Give feedback.
All reactions