background
React uses render props+HOC to support custom hooks, which are used to support logic reuse and state sharing in applications with complex functions. It basically fits into the Combination-based programming model of React; Redux and Mobx based global state management solutions are currently the hottest solution for state sharing, but hooks and context based solutions are becoming increasingly popular in the community. The Co-store, a lightweight react state management solution, was born. Here’s a look at the science of how these solutions compare with the React project
Part1: Design and access
redux
-
design
Redux attempts to make state mutations predictable by imposing certain restrictions on how and when updates can Happen (Redux tries to make state changes predictable by placing restrictions on how and when updates can happen)
Around this idea, Redux devised the following core concepts
- Store: a place where state is stored centrally
- Action: “change”, a normal JS object, is the only way to change the state
- Reducer: “change” a pure function that changes the corresponding state in the store according to actions
- Middleware: “strong” middleware, which can be interpreted as pre-processing action or enhancing store
-
Access to the
Because Redux is a framework independent global state management solution, access in React applications must work with React-Redux
// Create const rootReducer = (state, action) => {switch (action.type) {case 'XXX': return newState default: return state } } const store = createStore(rootReducer); React <Provider store={store}>... </Provider>Copy the code
mobx
-
design
31 Reacting to state changes is always better then acting on state changes.
Mobx designed the following core concepts around this idea
- State: Data that drives the application, such as an Excel spreadsheet with data
- Derivations: Anything derived from a state without any further interaction is derivations
- Computed values: Computed values derived from the current observable state. The Excel formula is derived from computed values
- Reactions: A side effect that automatically occurs when your state changes
- Action: Any piece of code that can change state, such as entering a new value in an Excel cell
-
Access to the
Because Mobx is a framework-independent state management solution, access to react applications must work with Mobx-React (or mobx-React-Lite).
// Create const store = () => useLocalStore(() => {return {... state, ... React <Provider stores={stores}>... </Provider>Copy the code
co-store
-
design
co-store attempts to define store according to function to converge state as much as Possible.(Co-store tries to define store according to function to converge state as much as possible)
Co-store is analogous to Custom hooks, which emphasize that creating a store as a minimum unit of functionality is only necessary if and only if your functionality needs to share state globally or across components; otherwise, converge state into the corresponding components as much as possible. Then compose components to generate the UI and compose hooks to customize the logic.
Around this idea, co-store designed the following core concepts
- State: indicates all states of a storage store
- Action: is the only way to change the state
- Subscription: Supports selective subscription status and derived data
-
Access to the
// Create const initialState = XXX const actionCreators = {XXX: () => XXX} Const store = createScopeStore(initialState, actionCreators) // Access React < store.provider >... </store.Provider>Copy the code
summary
Design and Access | redux | mobx | co-store |
---|---|---|---|
No matching libraries are required | react-redux | mobx-react(mobx-react-lite) | ✅ |
Support for split stores | ❌ | ✅ | ✅ |
thought | Make state mutations predictable | Respond to state changes | Convergent states as much as possible |
Part2: Read and write status
redux
By defining mapStateToProps and mapDispatchToProps as parameters of the connect method provided by React-Redux, state and Dispatch in store in Redux are passed to the component as props. In other words, redux in React uses props to read and write state.
const mapStateToProps = state => ({... }) const mapDispatchToProps = dispatch => ({... }) const Component = (props) => (...) export default connect(mapStateToProps, mapDispatchToProps)(Component)Copy the code
mobx
Context is used to combine defined stores, useStores(based on useContext) is used to obtain corresponding stores, and useObserver(Mobx-React-Lite) is wrapped to subscribe to the status. By directly calling store properties (including state and Action) to change the state, MOBx responds correctly to the changed state. In other words, mobx in React reads and writes state via the object being observed and this.
const {xxxStore} = useStores() const Component = (props) => useObserver(() => (...) ) export default ComponentCopy the code
co-store
Each store has a specific function, directly introduced, through the store provided useStore, useActions to achieve state reading and writing.
const [state, actions] = xxxStore.useStore()
const Component = (props) => (...)
export default Component
Copy the code
summary
State, speaking, reading and writing | redux | mobx | co-store |
---|---|---|---|
Without this | ✅ | ❌ | ✅ |
Immutable state | ✅ | ❌ | ✅ |
Manual mapping is not required | ❌ | ✅ | ✅ |
Native asynchronous support | ❌ | ✅ | ✅ |
Part3: Selective subscription and derived data
redux
The state management implemented by Redux in combination with React-Redux does not support selective subscription and derived data. The state is passed to the component as props and needs to be optimized by the component itself.
mobx
By wrapping the component useObserver, Mobx can implement the corresponding update only when the subscribed state node changes, thus realizing the selective subscription. The derived data (computed properties) is implemented by defining the property’s GET method when defining a store.
// The Component is updated when xxxStore.state1 is changed const {xxxStore} = useStores() const Component = (props) => useObserver(() => (... xxxStore.state1...) Const store = useLocalStore(() => {return {...) const store = useLocalStore(() => {return {... state, get computedState1(){ return this.state.state1... }... actions } })Copy the code
co-store
In line with the idea of defining stores through functions, co-stores need to declare subscriptions in advance when they are created, and then use the useSubscribe provided by the store to implement selective subscription status and derived data
Const subscriptions = {// Only subscriptions to state1 will be re-rendered if state1 changes: (state) => state1, // Subscribed computedState1, rerender computedState1 when dependent state1 changes: (state) => state1... } const store = createScopeStore(initialState, actionCreators, Subscriptions) // Use const state1 = xxxstore.usesubscribe ("state1") const computedState1 = xxxStore.useSubscribe("computedState1")Copy the code
summary
Selective subscription and derivative data | redux | mobx | co-store |
---|---|---|---|
Without this | ✅ | ❌ | ✅ |
Custom update | ❌ | ✅ | ✅ |
Derived data (calculated attributes) | ❌ | ✅ | ✅ |
Part4: Performance comparison
Performance comparison is actually very troublesome, because it is difficult to find a standard to measure. After searching a lot of data, I think the comparison scenario of Mobx and Redux performance comparison can be used for reference. The following is based on this scenario. A direct comparison of mobx and Co-Store time in a scenario where 1000 components subscribe to the same store:
- Average duration of five MOBx attempts: 10.6538s
- Average duration of five times in Co-store: 10.0624s
It is not hard to see that mobx and Co-Store performance is not much different for the above scenario (we can pretend that co-store≈ MOBx > redux), but the comparison here is also for the scenario, not very strict. Just to show that the store based on the Context implementation is not bad in performance, after all, it is the official product of react
Part5: Actual Combat ·Todos
redux
Here is an example from the official website:redux-todos
mobx
Codesandbox:mobx-todos
co-store
Codesandbox:co-store-todos
Write in the last
Redux and Mobx based global state management solutions recommend store state management. In some ways, it doesn’t fit with the React custom hooks logic. Use context+ publish/subscribe mode to determine whether other components need to be updated when the state of one component is updated. Context + publish/subscribe mode is used to determine whether the state of another component needs to be updated. A complex set of hooks that combine state with side effects and other logical obfuscations is possible. In addition, split multiple hooks to combine stores, requiring manual maintenance of the sequence of providers when stores depend on each other.
This is not good, according to the routine, this is the time to promote a wave of co-store! In other words, determine whether you really need it before you write a Store, determine whether you need state management based on the requirements scenario and functionality, and if so, find the best solution for that scenario. If a requirement scenario (such as the application design layer) desperately needs event sourcing, Redux is a great choice. If you’re used to MVC’s separation of view and data layers and are comfortable navigating the seas of this, function-based declarative stores may not be your cup of cards, and Mobx is already pretty good at this.
In the case of the Co-store, react-focused hooks+function Component (which can also support class Component via encapsulation). It’s a matter of state sharing. Encapsulate your logic according to the function, and define your store according to the function. I think react combines declarative UI based on this pattern, perhaps practicing best practices in algebra effects
About algebraic effects, I amway Daniel’s algebraic effects with React in this article. In addition, if you are interested in co-store, here is the source code portal, welcome to discuss and exchange ~