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:

  1. State module, also known as “state slice”, is introduced in this paper
  2. Lazy loading of status modules
  3. 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:

  1. Mobx and Redux both need a “bonding assistant”, but why not Natur?
  2. 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:

  1. I can’t tell which states are global and which are local (probably not necessarily).
  2. Put them all in, so many states are all mixed together, then the module will be bloated.
  3. 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.