Redux is a data management layer that is widely used to manage data for complex applications. But in practice, Redux is poorly used, and arguably not well used. And at the same time, a number of data management solutions have emerged in the community, Mobx being one of them.
Problems with Redux
This is the position Redux has given itself, but there are many problems with it. First, what does Redux do? Looking at Redux’s source code,
createStore has only one function that returns 4 closures. The
dispatch only does one thing, it calls the
reducer and then the
listener of the
subscribe, where the
state is immutable or mutable, all controlled by the user, and Redux doesn’t know if the state has changed, let alone where it has changed. So, if the view layer needs to know which part needs to be updated, it can only do so by dirty checking.
Look at what
react-redux does, it hangs a callback on store.subscribe, calls
connect to pass in
mapDispatchToProps every time a subscribe occurs, and then dirty checks every item in
props. Of course, we could use the immutable data feature to reduce the number of props and thus reduce the number of dirty detections, but where is the nice thing to have props all coming from the same subtree?
So, if there are n components connected, every time an action is dispatched, no matter what granularity of update is done, O(n) time complexity of dirty detection will occur.
Worse still, each time the reducer is executed Redux calls the listener directly, and if multiple changes (e.g. user input) occur in a short period of time, the immutable overhead, combined with the overhead of redux matching actions with strings, the overhead of dirty detection, and the overhead of the view layer, the overall performance can be very bad, even when the user input The performance can be very bad, even when the user input is often updated with only one “input”. The larger the application, the worse the performance. (Here the application refers to a single page. The single page here does not mean a single page of SPA, because with Router, all components of the page that is cut away are unmounted)
As the size of the application increases, and the number of asynchronous requests increases, the
Predictable advertised by Redux is simply a bubble, and more often than not, it is reduced to a data visualization tool with various tools.
Mobx is arguably the most complete of many data solutions. mobx itself independent, not interdependent with any view layer framework, so you can choose the appropriate view layer framework at will (except for some, such as Vue, because they are the same principle).
Mobx (3.x) and Vue (2.x) currently use the same responsive principle, to borrow a diagram from the Vue documentation.
Create a Watcher for each component, add hooks to the getter and setter of the data, trigger the getter when the component is rendered (e.g., call the render method), and then add the Watcher corresponding to this component to the dependency of the data associated with the getter (e.g., a Set). When the setter is triggered, it knows that the data has changed, and then the corresponding Watcher goes to redraw the component at the same time.
In this way, the data needed by each component is precisely known, so when the data changes, it is possible to know exactly which components need to be redrawn, and the process of redrawing when the data changes is O(1) time complex.
Note that in Mobx, the data needs to be declared as observable.
In this article , the authors use a 128*128 drawing board to illustrate the problem. Since Mobx uses
setter (a parallel
Proxy-based version may appear in the future) to collect the data dependencies of component instances, every time a point is updated,
Mobx knows which components need to be updated and the time complexity of the process of deciding which component to update is O(1), while
Redux gets which components need to be updated by dirty checking each
connect component, and with n components
Although, after a series of optimizations, the Redux version can get a performance that does not lose the Mobx version, when Mobx can get a good performance without any optimization. The most perfect optimization of Redux is to create a separate store for each point, which is the same idea as a bunch of solutions like Mobx that pinpoint data dependencies.
Mobx State Tree
Mobx is not perfect; Mobx does not require the data to be in a tree, so it is not easy to make Mobx data cubic or record every data change. Based on Mobx, the Mobx State Tree was born. Like Redux, the Mobx State Tree requires data to be in a tree, making it easy to visualize and track data, which is a boon for developers. The Mobx State Tree also makes it very easy to get accurate TypeScript type definitions, which is not easy to do with Redux. Runtime type safety checks are also provided.
Mobx State Tree also provides the
snapshot function, so that although the MST data itself is variable, it can still hit the effect of immutable data. Officially, you can use
snaptshot directly in conjunction with Redux development tools to facilitate development, and you can also use the MST data as a Redux store; of course, you can also use snapshot to embed the MST in a Redux store as data (similar to what is popular in Redux Immutable.js).
And, in MST, variable data and immutable data (snapshot) can be transformed into each other, and you can apply snapshot to data at any time.
setter to collect data dependencies of components so that it knows exactly which components need to be redrawn when the data changes. When the interface gets large, there are often many fine-grained updates, and while responsive design has additional overhead, at large interface sizes this overhead is far smaller than doing dirty checks on each component. So in this case Mobx will easily get better performance than Redux. Dirty check-based implementations have better performance than Mobx responsive ones when all the data changes, but this is rare. Also, some benchmarks are not best practices, and their results do not reflect the real situation.
React itself provides mechanisms to reduce useless rendering using immutable data structures (e.g. PureComponent, functional components), and because some of React’s ecology is bound to Immutable (e.g. Draft.js), it is not so comfortable when working with variable observer pattern data structures. So, it is recommended to use Redux and Immutable.js with React until you encounter performance issues.
The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.
splice, otherwise it is not detected.
Due to the principle of Mobx, to do accurate on-demand updates, you have to trigger the getter in the right place, and the easiest way to do that is to render the data to be used and only deconstruct it in render.
mobx-react From 4.0 onwards, the structure in the map function accepted by
inject is also tracked, so it can be written directly in a way similar to
react-redux. Note that prior to 4.0 inject’s map functions were not tracked.
Responsive has additional overhead that can have a performance impact when rendering large amounts of data (e.g., long lists), so use
observable.allow (Mobx), and
types.frozen (Mobx State Tree) in a sensible way.