ViewStore State's deduplication with DeduplicationScope #527
Replies: 3 comments 6 replies
-
I like the idea and that's a nice use of resultbuilder ! I still see value in having a type containing the view's state, it limits which properties of the state are available to the layout and serve as developer documentation. I also often start a feature without TCA integration by poking layouts using static viewStates. When I have my viewState and viewAction ready, I work toward integrating with TCA and the rest of the app. I wonder if ResultBuilders could be used to define the type of the viewState in a way that would make only interesting properties available. |
Beta Was this translation helpful? Give feedback.
-
This seems like a great idea to reduce boilerplate! Idea presented 10 months ago, I'm curious why this idea hasn't been picked up..? @tgrapperon maybe open a PR? Or is your solution incompatible with new versions of TCA? I guess a lot has happened since May 2021? Cheers! |
Beta Was this translation helpful? Give feedback.
-
I'm curious why any View object has a ViewState in it at all now that TCA has "WithViewStore" functionality (as presented in swift-composable-architecture's README):
I've been looking at some of isowords to learn more about TCA and am finding these explicit ViewStates everywhere: https://github.com/pointfreeco/isowords/blob/main/Sources/HomeFeature/Home.swift#L541:
Why not just do something like this:
At what point is it better to create a ViewState class within a View over using the really nifty WithViewStore? I feel like explicitly adding ViewStates to Views just creates redundant boilerplate code, which makes the View struct more bloated than it needs to be. Am I missing something? Or has isowords just not been updated to replace all ViewState structs with WithViewStores because there are higher priority items to work on? |
Beta Was this translation helpful? Give feedback.
-
One of the main issues with Redux-like architectures is the diffing of gigantic app states. TCA elegantly solves this by introducing
ViewStore
’s. They allow to observe only a relevant portion of a possibly very complex state, and in a SwiftUI app, they prevent unwantedView
invalidations when none of the relevant properties of aView
has changed.A classic way to restrict
ViewStore
to only a portion ofState
is to use an internalViewState
. ThisViewState
isEquatable
and the store’s state is mapped to aViewState
via store’s scoping. This approach can be observed in isowords, like inGameView
for example (slightly modified for explanatory purposes):As we can see, there is no specific logic in
ViewState
. It’s only anEquatable
way to wrap a subset ofGameState
’ properties. Its only purpose is to provide access to these properties and make theViewStore
publish on itsobjectWillChange
publisher when they change. The wholeViewState
struct definition is 100% boilerplate in this specific case.Furthermore, these
ViewState
’s don’t work out of the box withbinding
higher-order reducers, as they expectViewStore.State == Store.State
.The
DeduplicationScope
I’m proposing allow to useViewStore<State, Action>
directly, and to specify a set of relevant properties used for deduplication in a very concise way. As an example,GameView
becomesAs you can see, the whole
ViewState
is gone,ViewStore.State == GameState
, and we list a fewGameState
’s keypaths we want to watch for changes in a closure after theViewStore
’s instantiation. Furthermore, none of the code below these lines has to be modified. In this example, we are using Swift 5.4@resultBuilder
, but a chaining API is also provided:In other words, and under the hood,
DeduplicationScope
is a concise way to build theremoveDuplicates
closure ofViewStore
. We avoid scoping the store for the only benefit of diffing a portion of its state, we don’t instantiate a whole new struct (and most importantly, we don’t have to define it), we can use thebinding
higher-order reducer out of the box, and we can access constant or non-equatable properties ofState
.As a side effect of the implementation, observed properties are checked in the order they’re listed, and we can use this to our advantage to optimize comparison if needed.
The principal issue is the possibility to access an unobserved variable property in
State
if we forget to list it in theViewStore
’s instantiation. This problem is also frequent in vanilla SwiftUI if we forget to wrap properties with@Published
. I guess one can modify the internals ofViewStore
and log a message at runtime if the user is accessing a non-observedKeyPath
. It could worth the effort if @mbrandonw or @stephencelis green-light it for TCA at some point. In the current state, the proposal is purely additive and can be implemented as a third-party library.This also adds a few overloads to
ViewStore
, but it also helps to remove a bunch of code. I guess they can be refactored if needed.ViewState
’s are still very convenient when they carry some logic by themselves (for example, when they define new properties derived fromState
). This proposal aims at the trivial ones.Please let me know what you think!
Should I document the code and prepare a PR with a case study?
Here is a complete first implementation of
DeduplicationScope
:Beta Was this translation helpful? Give feedback.
All reactions