5 ms·
I have written several large WPF applications that use the MVVM pattern and it has worked very nicely. So I have always been a little confused as to why web dev
by PhilWright 11y ago
I have written several large WPF applications that use the MVVM pattern and it has worked very nicely. So I have always been a little confused as to why web developers have struggled to write web apps using the same approach. The desktop apps I have worked on are easily bigger than any SPA I have ever seen. So I have not seen any issue with it working when scaling MVVM to large code bases.
Maybe there is something fundamentally different about web development and/or JavaScript that causes it to not work well?
For example, lets say you have a User model object. You want to display information about the user in three different places in the user interface. With MVVM this is simple. You have a single User model and three different VM instances that each point at the source model. Each VM has a matching View. Any change in the User model propagates down to the VM and then the View. If you edit the users name in the View it updates the VM and then the User model. Changes then cascade back down so the other two views update if needed. Very simple.
But how is this done in Flux/Redux? Does the store contain the user information just once? But then what about the intermediate information that is the equivalent of the three views? I presume you need to store this state information in some store as well. But then how do you handle updating the user and having the three other stores updated at the same time as needed? I am not a web developer so this might be trivially simple to those that know how it fits together.
- rjayatilleka 11y agoIn Redux, if you edit the user's name, that would dispatch/trigger an action {type: 'SET_USER_NAME', payload: 'New Name'}. The store would take the current state and the action and generate the new state. Then it would send it to React to cause a re-render of everything to the virtual DOM (not just everywhere the username was, but actually the entire view). And then it diffs the vDOM with the real DOM and updates the real DOM anywhere it's different.
- ry_ry 11y agoSlightly pedantic point, but I was under the impression the diff didn't touch real DOM unless there was a change required, and just dirty checked the virtual DOM - ie. doing everything in memory without a bunch of DOM access was where the performance came from?
- samizdatum 11y agoIn WPF, you call OnPropertyChanged on the setter of a property, giving you two-way data binding. Great, now you can modify the model directly from the view. The problem is that modifying the models directly from the views makes it really difficult to reason about your dataflows. It's not unusual to have the same model bound to five or ten different views. Run your WPF application for a while, and- hang on- this view isn't showing the correct data. This kind of scenario is hellish to debug, because there's so many places that could have mutated the model. In a large application using two-way binding, the application state is implicit in its execution. There's thousands of places that mutate the state, so many that if you tried to make a graph of the dataflows, you'd get spaghetti. Sure, the code is neat and simple, but you know you've got a problem when the dependency graph is so complex that you can write a cyclic binding and not even realise it. Redux borrows a lot from functional techniques to tackle these kinds of problems. The big one is that you can't mutate the state, because it's immutable. Instead, you write reducing functions that take an application state and an action, and return a completely new application state based on that action (which might sound wasteful until you learn about functional data structures). This buys you a lot of things. You can hang on to your old application states instead of throwing them away, and then, very easily, set up a slider that lets you "time travel" in your application. You can grab a list of actions, and feed them to a hundred instances of your application, all with different initial states, and inspect how the state changed at each action- really useful for testing or debugging. You can completely change your code, and have the application update in real time with all the state intact, but now running your new code- this really speeds up development. You can catch errors, and then automatically send a complete snapshot of the application state (complete with where the focus was, the text the user was typing, the clicks they used to get to that particular screen, etc.) and then play it back, allowing you to perfectly reproduce the error. These things are all extremely difficult when the state of your application (and how it changes over time) is implicit and scattered over hundreds of views. It's trivial when the state of your application is completely described by an initial state, a series of actions, and a single reducing function. Regarding the "intermediate information" you alluded to: this is generally solved by using something like reselect, which allows you to define transformations on the state that can be fed to views. For example, you could write a function that returns a list of items in the state that match a search query in the state, but not have the resulting list itself live in the state. This means you still have one source of truth, and the immutability of the state ensures you don't lose any of the benefits I listed above.
- Lazare 11y agoIt's actually fairly similar in an idiomatic Redux app. "Lets say you have a User. You want to display information about the user in three different places in the user interface. With Redux this is simple. You have a single User store and three different containers that each receive the source model as inputs, transform it using connect/reselect, and then passes it on, along with action creators to change the data, to the underlying components. Any change in the User model propagates down to through the containers to the components. If you edit the user from one component it updates the store and then the components. Changes then cascade back down so that all three containers update if needed. Very simple." Sounds familiar, no? The big difference is that instead of editing the data you're passed directly, and having it magically update everywhere, you're passed an immutable User object and an updateUser() function, which you call to edit the object. But the same distinction between your data, the view, and the intermediate layer that transforms the data for the view is very much present.