At first, I learned about Redux through Umi. At that time, Dva was based on Redux. It was not bad to use, but a little annoying. Later, WHEN I used Mobx, I was fascinated by the simplicity and randomness of TA, so I chose Mobx. But when I used it, I always missed Redux unconsciously. Gradually, I understood TA, and THEN I realized that constraint is not an excessive requirement, and NOW I just want to escape from chaos. The more it feels right.
A view of the state management library
In the development of medium and large projects, state management libraries are often used. Currently, Redux and Mobx are the mainstream ones. I think the biggest difference between them is the concept, that is, whether state data is immutable.
Cut to the chase
First, LET me talk about my opinion. I made a judgment based on two aspects. One is short-term improvement and the other is long-term maintenance.
Difficulty: Redux>Natur>Mobx
Maintenance difficulty: Mobx>Natur>Redux
Natur is a better fit for me, a developer who often works on mid-sized, mid-range projects.
Then I will briefly analyze Natur from these two aspects.
Natur’s hands-on analysis
Post the full code first:
import { createStore, createInject } from "natur";
import{... }from 'natur/dist/middlewares'; // Introduce built-in middleware
/ * -- -- -- -- -- -- -- -- -- -- -- -- -- equipment - start -- -- -- -- -- -- -- -- -- -- -- -- - * /
// Create state Module -Module (state slice)
const testModule = {
// State data
state: {
testValue: 0,},// Calculate attributes
maps: {... },// Modify the state action
actions: {... }};// Create store-store
export const naturStore = createStore(
// State module, also known as "state slice"
{ testModule },
// Lazy loading of modules
{},
/ / configuration
{
// Middleware, as recommended by the official website
middlewares: [...]. });/ * -- -- -- -- -- -- -- -- -- -- -- -- -- equipment - over -- -- -- -- -- -- -- -- -- -- -- -- - * /
/*------------- provides injection -start-------------*/
// Provide the ability to inject its data externally
export const injectNaturStore = createInject({ storeGetter: () = > naturStore });
/*------------- provides injection -over-------------*/
Copy the code
Before using the state management library, we need to know how to create it before we can talk about how to equip page components for use.
Create state store-store
The following code
/ * -- -- -- -- -- -- -- -- -- -- -- -- -- equipment - start -- -- -- -- -- -- -- -- -- -- -- -- - * /
// Create state Module -Module (state slice)
const testModule = {
// State data
state: {
testValue: 0,},// Calculate attributes
maps: {... },// Modify the state action
actions: {... }};// Create store-store
export const naturStore = createStore(
// State module, also known as "state slice"
{ testModule },
// Lazy loading of modules
{},
/ / configuration
{
// Middleware, as recommended by the official website
middlewares: [...]. });/ * -- -- -- -- -- -- -- -- -- -- -- -- -- equipment - over -- -- -- -- -- -- -- -- -- -- -- -- - * /
Copy the code
The repository is created using the createStore API with three parameters:
- State module, also known as “state slice”, is introduced in this paper
- Lazy loading of status modules
- Configuration, Natur provides a number of configuration items, and for the first use, the main is “middleware” **
Configuration – Middleware
This part is finished by using the official recommended scheme directly. This article will not go into further details. The code is as follows.
{
middlewares: [
thunkMiddleware, // Action supports returning functions and retrieving the latest data
promiseMiddleware, // action Supports asynchronous operations
fillObjectRestDataMiddleware, // Incremental update/overwrite update
shallowEqualMiddleware, // New state shallow comparison optimization
filterUndefinedMiddleware, // Filter actions with no return value
devTool, // Development tools],}Copy the code
All of this introduction to Natur in this article is based on this official middleware configuration.
Lazy loading of status modules
Asynchronous implementation is based on the synchronous module using the dynamic import, pseudo-code is as follows:
const module1 = () = > import('Sync status module');
Copy the code
Status Module -Module
A Module is an object consisting of state, maps, and actions. It looks like this:
// Create state Module -Module (state slice)
const testModule = {
// State data
state: {
testValue: 0,},// Calculate attributes
maps: {... },// Modify the state action
actions: {... }};Copy the code
state
It’s just a normal object.
state: {
number: 1.value: 2
},
Copy the code
maps
Evaluate properties.
maps: {
// The first element of the array declares the map's dependency on state. The last function retrieves the previously declared dependency, and you can implement whatever you want in it
sum1: ['number'.'value'.(number, value) = > number + value],
// You can also declare dependencies as functions, which is useful for complex type states
sum2: [state= > state.number, s= > s.value, (number, value) = > number + value],
// It can also be a function that depends directly on the entire state. The disadvantage is that the function is re-executed whenever the state is updated, without caching
sum3: ({number, value}) = > number + value,
// It can also be a function that has no dependencies and executes only once
isTrue: () = > true,}Copy the code
actions
The way to change the state, whether synchronous or asynchronous, is configured under this option. Action is a function, just like Redux.
Synchronous:actions: {...// Update state asynchronously
testAsyncAction: (value: number) = > {
return { value: value }; }... }Copy the code
Asynchronous - Returns a promise:actions: {...// Update state asynchronously
testAsyncAction: (value: number) = > {
return Promise.resolve({ value })
}
...
}
Copy the code
Asynchronous - Return function:actions: {...// Update state asynchronously
testAsyncAction: (myParams) = >{
return ({ setState }) = > {
setTimeout(() = > {
setState(Date.now())
}, 5000); }}... }Copy the code
Use on page components
Creating an injector
.export const injectNaturStore = createInject({ storeGetter: () = >naturStore }); .Copy the code
Create an injector Hoc using the repository created so that you can inject the status module into the Props of the component.
Using an injector
.// Import and use the injector
import { injectNaturStore } from "@DATA_MANAGER/index";
let injecter = injectNaturStore(["testModule"]);
type PropsT = {
[prop: string]: any;
} & typeof injecter.type;
// Page component
class TestViewUI extends React.Component<PropsT>{... } injecter(TestViewUI)Copy the code
Use status Module data
this.props.lightHomeStoreN? .state? .testValueCopy the code
Modify status Module data
Synchronous and asynchronous are consistentthis.props.lightHomeStoreN.actions.testAsyncAction("Test asynchronous responses!");
this.props.lightHomeStoreN.actions.testSyncAction("Test synchronous response!");
Copy the code
Set the granularity of page response status Module updates
Listen to all of the modules
As soon as the injected module changes, it updates the page component, triggering render.
injectNaturStore(["testModule"]) or injectNaturStore ("testModule")
Copy the code
Part of the listening Module
As soon as the injected module changes, it updates the page component, triggering render.
let complexInjector = inject(
['testModule', {state: [s= > s.xxx], maps: ['xxx']}],
['other', {state: [s= > s.xxx], maps: ['xxx']}]); Or complexInjector = inject ('app'.'other')
.watch('app', {})
.watch('other', {state: [s= > s.xxx], maps: ['xxx']})
Copy the code
Optimization is always done when needed, and Natur provides a way to optimize.
contrast
Natur has modules directly, which is very heartwarming
That’s one of the things I like about Mobx. Redux
const testReducer = (state = { testValue:0 }, action = {}) = > {
const { type, payload } = action;
switch (type) {
case "action_1":
return Object.assign({}, state, {
testValue: state.testValue+1
});
case "action_2":
return Object.assign({}, state, {
testValue: state.testValue-1
});
case "action_3":
return Object.assign({}, state, {
testValue: payload
});
default: returnstate; }};Copy the code
Compared with Redux, it is really good to directly divide modules into Reducer. Redux can also split modules by itself, just like Dva, and then mark with nameSpace. However, “there is” and “diy” itself, which is different.
Just like sharing a room, do you think a partition can be the same as a separate room?
I think it’s good to have the experience.
In terms of page use, Natur is primitive, but in some ways better
Combine with react
The use of Redux and Mobx on pages is similar, with the help of other “binding assistants” such as Reudx-React and Mobx-
Question:
- Mobx and Redux both need a “bonding assistant”, but why not Natur?
- So what is the significance of this “bonding assistant” being set up separately?
I think Mobx and Redux are considering not only the React side to combine, so they separate out the “combine” part and make the “combine assistant” for different sides.
In this case, Natur doesn’t need to combine with the React side. This should be easier for React users…
The way Natur repository is used on the page
Em ~ ~ ~ is very primitive, very direct, and the benefits are very practical. If you use Ts development, there will be associative hints, such as:
It’s actually great. Look at Redux:
dispatch({
type: 'settings/changeSetting'.payload: config,
})
Copy the code
You can say Redux’s Dispatch is sophisticated and concise, but admittedly, a bit of a puzzle.
I want to know where the destination is, and I have to search, even globally… uncomfortable
At first glance, the original method is simpler and clearer, with key points and hints, saving me the trouble of remembering “mailing address”.
It’s harder than Mobx, save it
What’s the comparison? There’s so much going on in Mobx? In non-strict mode, you can change the state if you want. You may not understand how it can be observed, and you can get drunk looking at observable objects when debugging, but it’s not a big deal.
My point of view
Based on the above subjective analysis, I have reached my opinion.
Difficulty: Redux> Natur >Mobx
Maintenance analysis of Natur
Maintenance are different from those analysis, the latter mainly embarks from the basic details, gradual development, until the full functionality, strategy is from the bottom up, maintenance analysis I think have to be reversed, with the strategy from top to bottom, from the concrete practical problems triggered, naturally, deduction, until a clear implementation scheme.
Growing pains of the project
One status Module per page is enough
From the perspective of an ordinary developer like me, my project must use the state management library. I divide the status modules according to the page, that is, one page corresponds to one Module. At the beginning, it was ok, but after completing some business functions, the volume was not large.
As the project develops and the status modules for each page become more complex,
Moreover, there is some interaction between the state models, which is called a complex relationship.
What if we don’t have enough
It’s okay to get status module data from the parent and pass it down in one direction. But as the child component gets more complex and has its own child component, one-way becomes more taxing. Then the child component wants to get out of one-way. Access the page status module directly, ok?
Subcomponent wants to cross the line. No, not yet
Children skip the parent component with data directly, out this line, is not absolutely not, but not in time, if the child component to get the page status data, do the father should have components, then promote is divided, the parent of page components and sub components should be better combined with unity, rather than legislation, so this kind of behavior is not desirable, so have to solve problem, This is the state Module to start with.
It’s a bit of a mess
You want to split an entire page status module into qualified sub-components of the page, such as
First of all, the subcomponents corresponding to the split status module are very related, so they are bound to communicate with each other, and the relationship can be old, such as
Some module has to step up first
So the relationship is so complex, let the successful slimmer page component module unified management, after all, the status is still there, in order to maintain the order between the status modules, the main thing to do is cross-module communication and business processing
But gradually the page component module and fat, this is not only fat, but also a little do not keep the “duty”, this can not ah.
Natur-service is very intellectual
After some coder to achieve the basic functions of the project, to the pursuit of quality, don’t want to endure chaos, hope everything looks in order, but often blocking script is always blocking script, the heart of progress is so stubborn, dilemma, and natur – service know what to do to organize your code, free from confusion, With him, the situation became as follows:
You could even do that
You can further divide the heap based on your understanding of the business.
Development and use of Natur-service
Install the natur – service
yarn add natur-service
Copy the code
Create an instance of natur-service
Let’s write a store for the test example testStore.ts
import { createStore } from 'natur';
const count = {
state: 1.actions: {
inc: (state) = > state + 1.dec: (state) = > state - 1,}}const modules = {
count,
count1: count,
};
const lazyModules = {};
export const store = createStore(modules, lazyModules);
export type M = typeof modules;
export type LM = typeof lazyModules;
Copy the code
Then create a new service.ts file in the same directory
import {store, M, LM} from "./testStore.ts";
import NaturService from "natur-service";
class CountService extends NaturService<M.LM> {
constructor() {
super(store); . }}// instantiate to start listening
const countService = new CountService();
Copy the code
Communication between modules
import {store, M, LM} from "store";
import NaturService from "natur-service";
class CountService extends NaturService<M.LM> {
constructor() {
super(store);
// Execute the inc method of the count module
this.dispatch('count'.'inc'.0).then(() = > {
// If count is an unloaded module, the action will not be triggered until count is loaded
// The old Dispatch would throw a fixed Promise error to clear the cache and prevent a stack burst if the same action was called several times before the load was complete
console.log('dispatch finish'); }}})// instantiate to perform push
const countService = new CountService();
Copy the code
Listen for module updates and update details
import {store, M, LM} from "store";
import NaturService from "natur-service";
class CountService extends NaturService<M.LM> {
constructor() {
super(store);
// Observe the count module, ModuleEvent see documentation
this.watch("count".(me: ModuleEvent) = > {
// This is an update
console.log(me);
// This is the business logic you need to execute
console.log('count module has changed.'); }); }}// instantiate to start listening
const countService = new CountService();
Copy the code
ModuleEvent
The attribute name | instructions | type |
---|---|---|
state | The latest state of the module | any |
type | Type of trigger module update. ‘init’ is triggered when the module is initialized, ‘update’ is triggered when the module’s state is updated, and ‘remove’ is triggered when the module is uninstalled | ‘init’ |
actionName | The name of the action that triggers module updates exists only if type is ‘update’ | string |
oldModule | Old module data, undefined when type is ‘init’ | InjectStoreModule |
newModule | New module data, when type is ‘remove’ is undefined | InjectStoreModule |
contrast
Let’s put all the states in the warehouse
This problem has been bothering me for a long time with both Dva (Redux) and Mobx, and AFTER a long struggle, I chose a compromise:
I’ll write component state first and then change that component state to warehouse state as needed.
There are several concerns about not having it all in storage:
- I can’t tell which states are global and which are local (probably not necessarily).
- Put them all in, so many states are all mixed together, then the module will be bloated.
- Would it be confusing to split modules and communicate across modules and listen to each other’s business?
With Natur-service, the problem is simple
Redux cross-module communication is a bit of a drag
Redux may focus on global state management and even split modules by itself. Dva has perfected Redux well, but there is still an embarrassing problem. If you are lazy, you will find that there are many effects from a model, but reducer has only one. After all, with so many effects, just a reducer is enough.
However, the reducer of cross-module communication triggers is all used by this reducer, so how to distinguish which action is triggered?
Communication across modules is easy, and module splitting is easy to manage
With Natur-service, the split module is much better, so that cross-module communication becomes orderly, and there is no coupling relationship between modules. Compared with DVA, you have to put to trigger effect or reducer of specified modules.
*testEffect(_, { call, put, select }) { ... Yield put({type: 'other module /effect or reducer', payload: data,}); . }Copy the code
Mobx goes further, importing this module directly to execute the corresponding action.
@action
fetchLightDelete = (params:any) = >{... otherModule.testAction() ... }Copy the code
Natur doesn’t have to write this in the module at all, you can just set up a flag state variable and the action that triggers it to change.
class CountService extends NaturService<M.LM> {
constructor() {
this.watch("flagAction".(me: ModuleEvent) = > {
this.dispatch('count'.'inc'.0).then(() = >{}); }}}Copy the code
Adding an unimportant flagState to trigger natur-service listening is a “contrivant” way of doing it, but it allows Natur-Servic to fine-tune cross-module communication.
Some of the operations that are consolidated and scheduled by multiple warehouses do not need to be mixed up in the UI layer.
There are some business scenarios that are very suitable for Natur-service. For example, the A page has A warehouse operation, and the home page and user page have some corresponding warehouse state operations. Generally, I want to do it in the UI layer, that is, to integrate the business logic in the function componentDidmount for the life cycle of the component.
But choose which page to write, it seems that who causes who to write, but on reflection, this is not coupled, why other pages of business should be written in the login page? This is not a good idea, but with Natur-service, it can be directly handed over to the end, save the entanglements where to write, write to write is easy to mess.
Take on Mobx for maintainability…
I don’t think there is a comparison, Mobx variable values, you can’t manage Mobx without a strict specification and some necessary means (make a uniform change entry function etc.), it’s too free and overdone.
My point of view
Based on the above subjective analysis, I have reached my opinion.
Maintenance difficulty: Mobx>Natur>Redux
In comparison, I really feel that Natur is easier to maintain, but Redux is a big brand, I guess it should be better…
conclusion
Through short-term and long-term maintenance, I found that Natur was suitable for me and could well meet the needs of my code organization. It was just right for me, who was often engaged in the development of slipping projects.
digression
As an ordinary front coder, whenever see a domestic library developers, especially without the ring, I will particularly attention and respect, and come up with enough enthusiasm to learn and share, I think a good library is like a light, can be used to heating power, at the same time should be more refract light out, and not to cover, it is shameful, It is futile, but an excellent light source is not afraid of covering up, also can not be covered, because only their own can let their own light.