Compare redux, mobx and concent in depth to let you have a funny way in developing react app.
Tags: react, concent
Published: 2020-04-13 12:23:21

Compare redux, mobx and concent in depth to let you have a funny way in developing react app.

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/banner-en2.png)

**[❤ star me if you like concent ^_^](https://github.com/concentjs/concent)**

## Preface
 
`redux` and` mobx` are themselves independent state management frameworks, each with its own abstract api, which has nothing to do with other UI frameworks (react, vue ...), this article mainly talks about the contrast effect of using it with `react` , So the `redux` and` mobx` mentioned below imply `react-redux` and` mobx-react` which are binding libraries that allow them to function in `react`, and` concent` itself is for `React` personalized development framework, data flow management is only one of the functions, and the additional features that enhance the development experience of React can be used as needed. Later, all the parts related to` react` in `concent` will be cut away Release `concent-core`, its positioning is similar to` redux` and `mobx`.
  
So the players who will appear in this article are
  
### redux & react-redux     
  * slogan      
   JavaScript state container, providing predictable state management 
 
  * design concept       
  Single data source, use pure functions to modify state


![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/1.png)

### mobx & mobx-react     
   * slogan：   
    Simple and scalable state management

   * design concept  
    
Anything that can be derived from the application state should be derived

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/2.png)

### concent  
  * slogan：   
    Predictable, zero-invasion, progressive, high-performance react development solution

  * design concept   
    Believe that the development method of integrating immutable + dependent collection is the future of react, enhancing the characteristics of react components, writing less and doing more.
  

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/3.png)
  
After introducing the background of the three, our stage is officially handed over to them, and we start a round of competition. Who will be your favorite one in the end?

### 结果预览
The following five contest rounds have more actual demo codes. Here, the comparison results will be notified in advance, so that the readers can quickly understand.

store configuration | concent | mobx | redux
  -|-|-|-
  Support separation | Yes | Yes | No
  No root Provider & use without explicit import | Yes | No | No
  reducer without `this` | Yes | No | Yes
  Store data or methods without manual mapping to components | Yes | Yes | No
  
 **[redux counter example](https://codesandbox.io/s/redux-counter-2mk1g)**  
 **[mobx counter example](https://codesandbox.io/s/counter-mobx-9kt50)**     
 **[concent counter example](https://codesandbox.io/s/concent-counter-y61if)**  
___

State modification | concent | mbox | redux
  -|-|-|-
  Based on the principle of immutability | Yes | No | Yes
  Shortest link | Yes | Yes | No
  ui source traceable | Yes | No | No
  Without this | Yes | No | Yes
  Atomic split & merge commit | Yes(based on lazy) | Yes(based on transaction) | No
  
___

Dependent collection | concent | mbox | redux
  -|-|-|-
  Support runtime collection of dependencies | Yes | Yes | No
  Precise rendering | Yes | Yes | No
  Without this | Yes | No | No
  Only one API is needed | Yes | No | No

 **[mobx example](https://codesandbox.io/s/mobx-login-dx4wl)**     
 **[concent example](https://codesandbox.io/s/concent-login-ylbl4)**    
  
___

   Derived data | concent | mbox | redux(reselect)
  -|-|-|-
  Automatically maintain dependencies between calculation results | Yes | Yes | No
  Collect dependencies when triggering to read calculation results | Yes | Yes | No
  Calculation function without this | Yes | No | Yes
  
 **[redux computed example](https://codesandbox.io/s/redux-login-6uqg8)**       
 **[mobx computed example](https://codesandbox.io/s/mobx-login-dx4wl)**      
 **[concent computed example](https://codesandbox.io/s/concent-login-ylbl4)**       

___
todo-mvc combat       
**[redux todo-mvc](https://codesandbox.io/s/todo-mvc-redux-49kg0)**     
**[mobx todo-mvc](https://codesandbox.io/s/todo-mvc-mbox-k42q1)**       
**[concent todo-mvc](https://codesandbox.io/s/todoapp-react-concent-fvgvc)**        

## round 1 - Code style first experience
The counter has been promoted to the stage countless times as a good guy in the demo world. This time we are no exception. Come to a counter to experience how the 3 framework development routines are( they are created using `create-react-app`). Organize the code in a **multi-module way**, and strive to be close to the code scenario of the real environment.

### redux(action、reducer)
Through `models`, the function is divided into different reducers by module, the directory structure is as follows

```
|____models             # business models
| |____index.js         # Exposed store
| |____counter          # Counter module related actions and reducers
| | |____action.js     
| | |____reducer.js     
| |____ ...             # Other modules
|____CounterCls         # Class component
|____CounterFn          # Function component
|____index.js           # Application entry file
```

> Here only the code is organized with the original template of redux. In the actual situation, many developers may choose `rematch`,` dva` and other frameworks based on redux for secondary packaging and improved writing, but it does not prevent us from understanding counter examples.

Construct counter's action

```js
// code in models/counter/action
export const INCREMENT = "INCREMENT";

export const DECREMENT = "DECREMENT";

export const increase = number => {
  return { type: INCREMENT, payload: number };
};

export const decrease = number => {
  return {  type: DECREMENT, payload: number };
};
```

Construct counter's reducer

```js
// code in models/counter/reducer
import { INCREMENT, DECREMENT } from "./action";

export default (state = { count: 0 }, action) => {
  const { type, payload } = action;
  switch (type) {
    case INCREMENT:
      return { ...state, count: state.count + payload };
    case DECREMENT:
      return { ...state, count: state.count - payload };
    default:
      return state;
  }
};

```

Combine `reducer` to construct` store` and inject into root component

```jsx
mport { createStore, combineReducers } from "redux";
import  countReducer  from "./models/counter/reducer";

const store = createStore(combineReducers({counter:countReducer}));

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);
```

Use connect to connect ui with `store`

```jsx
import React from "react";
import { connect } from "react-redux";
import { increase, decrease } from "./redux/action";

@connect(
  state => ({ count: state.counter.count }),// mapStateToProps
  dispatch => ({// mapDispatchToProps
    increase: () => dispatch(increase(1)),
    decrease: () => dispatch(decrease(1))
  }),
)
class Counter extends React.Component {
  render() {
    const { count, increase, decrease } = this.props;
    return (
      <div>
        <h1>Count : {count}</h1>
        <button onClick={increase}>Increase</button>
        <button onClick={decrease}>decrease</button>
      </div>
    );
  }
}

export default Counter;
```

The above example wrote a class component, and for the now hot `hook`,` redux v7` also released the corresponding api `useSelector`,` useDispatch`

```jsx
import * as React from "react";
import { useSelector, useDispatch } from "react-redux";
import * as counterAction from "models/counter/action";

const Counter = () => {
  const count = useSelector(state => state.counter.count);
  const dispatch = useDispatch();
  const increase = () => dispatch(counterAction.increase(1));
  const decrease = () => dispatch(counterAction.decrease(1));

  return (
    <>
      <h1>Fn Count : {count}</h1>
      <button onClick={increase}>Increase</button>
      <button onClick={decrease}>decrease</button>
    </>
  );
};

export default Counter;
```

Render these two counters, **[View redux example] (https://codesandbox.io/s/redux-counter-2mk1g)**

```jsx
function App() {
  return (
      <div className="App">
        <CounterCls/>
        <CounterFn/>
      </div>
  );
}
```

### mobx(store, inject)
When there are multiple stores in the application (here we can understand a store as a reducer block in redux, which aggregates data, derived data, and modify behaviors), mobx stores have multiple ways to obtain them, for example, directly where needed Introduced on member variables

```js
import someStore from 'models/foo';// Is an instantiated store instance

@observer
class Comp extends React.Component{
    foo = someStore;
    render(){
        this.foo.callFn();//call method
        const text = this.foo.text;//read data
    }
}
```

We are here to do in accordance with accepted best practices, that is, assemble all stores into a root store and hang it on the Provider, wrap the Provider with the entire application root component, and mark the `inject` decorator where it is used. Our The directory structure is ultimately as follows, no difference from the `redux` version

```
|____models             # business models
| |____index.js         # Exposed store
| |____counter          # counter module related store
| | |____store.js      
| |____ ...             # Other modules
|____CounterCls         # Class component
|____CounterFn          # Function component
|____index.js           # Application entry file
```

Construct counter's store

```js
import { observable, action, computed } from "mobx";

class CounterStore {
  @observable
  count = 0;

  @action.bound
  increment() {
    this.count++;
  }

  @action.bound
  decrement() {
    this.count--;
  }
}

export default new CounterStore();
```

Merge all `store` into` root store` and inject into root component

```jsx
// code in models/index.js
import counter from './counter';
import login from './login';

export default {
  counter,
  login,
}

// code in index.js
import React, { Component } from "react";
import { render } from "react-dom";
import { Provider } from "mobx-react";
import store from "./models";
import CounterCls from "./CounterCls";
import CounterFn from "./CounterFn";

render(    
    <Provider store={store}>
      <App />
    </Provider>, 
    document.getElementById("root")
);
```

Create a class component

```jsx
import React, { Component } from "react";
import { observer, inject } from "mobx-react";

@inject("store")
@observer
class CounterCls extends Component {
  render() {
    const counter = this.props.store.counter;
    return (
      <div>
        <div> class Counter {counter.count}</div>
        <button onClick={counter.increment}>+</button>
        <button onClick={counter.decrement}>-</button>
      </div>
    );
  }
}

export default CounterCls;
```

Create a function component

```jsx
import React from "react";
import { useObserver, observer } from "mobx-react";
import store from "./models";

const CounterFn = () => {
  const { counter } = store;
  return useObserver(() => (
      <div>
        <div> class Counter {counter.count}</div>
        <button onClick={counter.increment}>++</button>
        <button onClick={counter.decrement}>--</button>
      </div>
  ));
};

export default CounterFn;
```

Render these two counters, **[View mobx example] (https://codesandbox.io/s/counter-mobx-9kt50)**

```jsx
function App() {
  return (
      <div className="App">
        <CounterCls/>
        <CounterFn/>
      </div>
  );
}
```

### concent(reducer, register)
Just like redux, Concent also have a global single root state `RootStore`, in this root state the first layer of key is used as a module namespace, a module of concent must be configured with` state`, the remaining `reducer`,` computed `,` Watch`, and `init` are optional and can be configured as needed. If all the store modules are written to one place, the simplest version of` concent` is as follows

```js
import { run, setState, getState, dispatch } from 'concent';
run({
    counter:{// 配置counter模块
        state: { count: 0 }, // [Required] Define the initial state, which can also be written as a function () => ({count: 0})
        // reducer: { ...}, // [Optional] How to modify the status
        // computed: { ...}, // [Optional] Calculation function
        // watch: { ...}, // [Optional] Observation function
        // init: { ...}, // [Optional] asynchronous initialization state function
    }
})；

const count = getState('counter').count;// count is: 0
// count is: 1，如果有组件属于该模块则会被触发重渲染
setState('counter', {count:count + 1});

// If counter.reducer is defined, the changeCount method is defined
// dispatch('counter/changeCount')
```

After starting `concent` to load the store, you can register it in any other component or function component to belong to a specified module or connect multiple modules

```jsx
import { useConcent, register } from 'concent';

function FnComp(){
    const { state, setState, dispatch } = useConcent('counter');
    // return ui ...
}

@register('counter')
class ClassComp extends React.Component(){
    render(){
        const { state, setState, dispatch } = this.ctx;
        // return ui ...
    }
}
```

However, it is recommended to put the module definition options in each file to achieve the effect of clear responsibilities and separation of concerns, so for counters, the directory structure is as follows

```
|____models             # business models
| |____index.js         # Configure store modules
| |____counter          # Counter module related
| | |____state.js       # State
| | |____reducer.js     # Reducer function
| | |____index.js       # Exposing the counter module
| |____ ...             # Other modules
|____CounterCls         # Class component
|____CounterFn          # Function component
|____index.js           # Application entry file
|____runConcent.js      # Start concent 
```

Construct the counter's `state` and` reducer`

```jsx
// code in models/counter/state.js
export default {
  count: 0,
}

// code in models/counter/reducer.js
export function increase(count, moduleState) {
  return { count: moduleState.count + count };
}

export function decrease(count, moduleState) {
  return { count: moduleState.count - count };
}
```

Two ways to configure store      
- Configured in the run function

```js
import counter from 'models/counter';

run({counter});
```

- Configured through the `configure` interface, the` run` interface is only responsible for starting concent

```js
// code in runConcent.js
import { run } from 'concent';
run();

// code in models/counter/index.js
import state from './state';
import * as reducer from './reducer';
import { configure } from 'concent';

configure('counter', {state, reducer});// 配置counter模块
```

Create a function component

```jsx
import * as React from "react";
import { useConcent } from "concent";

const Counter = () => {
  const { state, dispatch } = useConcent("counter");
  const increase = () => dispatch("increase", 1);
  const decrease = () => dispatch("decrease", 1);

  return (
    <>
      <h1>Fn Count : {state.count}</h1>
      <button onClick={increase}>Increase</button>
      <button onClick={decrease}>decrease</button>
    </>
  );
};

export default Counter;
```

Function components are written according to the traditional "hook" style, that is, every time the "hook" function is rendered and executed, the basic interface returned by the "hook" function is used to define an action function that meets the following conditions: the current Business needs.

However, since Concent provides the `setup` interface, we can use its ability to execute only once before the initial rendering, and place these action functions inside the` setup` as static functions to avoid repeated definitions, so a better function component should be

```jsx
import * as React from "react";
import { useConcent } from "concent";

export const setup = ctx => {
  return {
    // better than ctx.dispatch('increase', 1);
    increase: () => ctx.moduleReducer.increase(1),
    decrease: () => ctx.moduleReducer.decrease(1)
  };
};

const CounterBetter = () => {
  const { state, settings } = useConcent({ module: "counter", setup });
  const { increase, decrease } = settings;
  // return ui...
};

export default CounterBetter;

```

Create a class component and reuse the logic in `setup`

```jsx
import React from "react";
import { register } from "concent";
import { setup } from './CounterFn';

@register({module:'counter', setup})
class Counter extends React.Component {
  render() {
    // this.state has the same effect as this.ctx.state
    const { state, settings } = this.ctx;
     // return ui...
  }
}

export default Counter;
```

render these two counters, **[View example of concentration] (https://codesandbox.io/s/concent-counter-y61if)**

```jsx
function App() {
  return (
    <div className="App">
      <CounterCls />
      <CounterFn />
    </div>
  );
}
```

### Review and summary

This round shows the different code organization and structure when the three framework pairs define multi-module state

* `redux` wraps the root component through` combineReducers` with `Provider`, and also receives handwriting` mapStateToProps` and `mapActionToProps` to assist the component to obtain data and methods of the store
* `mobx` by combining multiple` subStore` into a `store` object and collaborating with` Provider` to wrap the root component, store data and methods can be obtained directly
* `concent` is configured through the` run` interface or the separate configuration of the `configure` interface, the data and methods of the store can be obtained directly

store configuration | concent | mobx | redux
  -|-|-|-
  Support separation | Yes | Yes | No
  No root Provider & use without explicit import | Yes | No | No
  reducer without `this` | Yes | No | Yes
  Store data or methods without manual mapping to components | Yes | Yes | No

## round 2 - State modification
The three frames have different styles of state modification.

In `redux`, the state modification path is strictly limited, so all actions to modify the state must dispatch an action, and then hit the corresponding` reducer` to synthesize a new state.

`mobx` has the responsive ability, you can directly modify it, but it also brings the annoyance that the data modification path cannot be traced back, resulting in` mobx-state-tree` to support the modification of data modification.

The modification of `concent` completely follows the` setState` modification entry style of `react`. On this basis, it further encapsulates the` dispatch`, `invoke`, and` sync` series APIs, and no matter which API is called Both can not only trace the complete link of data modification, but also include the source of triggering data modification.

### redux(dispatch)

Synchronous action

```js
export const changeFirstName = firstName => {
  return {
    type: CHANGE_FIRST_NAME,
    payload: firstName
  };
};
```

Asynchronous actions, completed with the help of `redux-thunk`

```js
// code in models/index.js, configure thunk middleware
import  thunk  from "redux-thunk";
import { createStore, combineReducers, applyMiddleware } from "redux";
const store = createStore(combineReducers({...}), applyMiddleware(thunk));

// code in models/login/action.js
export const CHANGE_FIRST_NAME = "CHANGE_FIRST_NAME";

const delay = (ms = 1000) => new Promise(r => setTimeout(r, ms));
// Tool function, assist in writing asynchronous actions
const asyncAction = asyncFn => {
  return dispatch => {
    asyncFn(dispatch).then(ret => {
      if(ret){
        const [type, payload] = ret;
        dispatch({ type, payload });
      }
    }).catch(err=>alert(err));
  };
};

export const asyncChangeFirstName = firstName => {
  return asyncAction(async (dispatch) => {//can be used for intermediate process multiple dispatch
    await delay();
    return [CHANGE_FIRST_NAME, firstName];
  });
};
```

### mobx version (this.XXX)

Synchronous action and asynchronous action

```js
import { observable, action, computed } from "mobx";

const delay = (ms = 1000) => new Promise(r => setTimeout(r, ms));

class LoginStore {
  @observable firstName = "";

  @observable lastName = "";

  @action.bound
  changeFirstName(firstName) {
    this.firstName = firstName;
  }

  @action.bound
  async asyncChangeFirstName(firstName) {
    await delay();
    this.firstName = firstName;
  }

  @action.bound
  changeLastName(lastName) {
    this.lastName = lastName;
  }
}

export default new LoginStore();
```

Direct modification

```js
const LoginFn = () => {
  const { login } = store;
  const changeFirstName = e => login.firstName = e.target.value;
  // ...    
}
```

Modify by action

```js
const LoginFn = () => {
  const { login } = store;
  const const changeFirstName = e => login.changeFirstName(e.target.value);
  // ...    
}
```

### concent(dispatch,setState,invoke,sync)
There is no longer any distinction between `action` and` reducer` in concent. The ui can directly call the `reducer` method. At the same time, the` reducer` method can be synchronous or asynchronous. It supports arbitrary combinations and lazy calls with each other, which greatly reduces the developer ’s mind. burden.

Synchronous `reducer` and asynchronous` reducer`

```js
// code in models/login/reducer.js
const delay = (ms = 1000) => new Promise(r => setTimeout(r, ms));

export function changeFirstName(firstName) {
  return { firstName };
}

export async function asyncChangeFirstName(firstName) {
  await delay();
  return { firstName };
}

export function changeLastName(lastName) {
  return { lastName };
}
```

The reducers can be combined arbitrarily. The methods in the same module can be directly called based on the method reference. The reducer function is not mandatory to return a new fragment state. It is also possible to combine other reducers.

```jsx
// reducerFn(payload:any, moduleState:{}, actionCtx:IActionCtx)
// When lazy calls this function, any one of the functions goes wrong, and all the states generated by the intermediate process will not be submitted to the store
export async changeFirstNameAndLastName([firstName, lastName], m, ac){
    await ac.dispatch(changeFirstName, firstName);
    await ac.dispatch(changeFirstName, lastName);
    // return {someNew:'xxx'};//可选择此reducer也返回新的片断状态
}

// View
function UI(){
    const ctx useConcent('login');
    // Trigger two renderings
    const normalCall = ()=>ctx.mr.changeFirstNameAndLastName(['first', 'last']);
    // Trigger a render
    const lazyCall = ()=>ctx.mr.changeFirstNameAndLastName(['first', 'last'], {lazy:true});
    
    return (
        <>
            <button onClick={handleClick}> normalCall </button>
            <button onClick={handleClick}> lazyCall </button>
        </>
    )
}
```

**[lazyReducer example](https://stackblitz.com/edit/concent-lazy-dispatch)**

Non-lazy calling process

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/4.png)

Lazy calling process

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/5.png)

Of course, except for `reducer`, the other three methods can be matched arbitrarily, and have the same synchronization state as` reducer` to other instances that belong to the same module and depend on a certain state

- setState

```js
function FnUI(){
    const {setState} = useConcent('login');
    const changeName = e=> setState({firstName:e.target.name});
    // ... return ui
}

@register('login')
class ClsUI extends React.Component{
    changeName = e=> this.setState({firstName:e.target.name})
    render(){...}
}
```

- invoke

```js
function _changeName(firstName){
    return {firstName};
}

function FnUI(){
    const {invoke} = useConcent('login');
    const changeName = e=> invoke(_changeName, e.target.name);
    // ... return ui
}

@register('login')
class ClsUI extends React.Component{
    changeName = e=> this.ctx.invoke(_changeName, e.target.name)
    render(){...}
}
```

- sync  

**[For more about sync, check the App2-1-sync.js file](https://codesandbox.io/s/green-tdd-g2mcr)**

```jsx
function FnUI(){
    const {sync, state} = useConcent('login');
    return  <input value={state.firstName} onChange={sync('firstName')} />
}

@register('login')
class ClsUI extends React.Component{
    changeName = e=> this.ctx.invoke(_changeName, e.target.name)
    render(){
        return  <input value={this.state.firstName} onChange={this.ctx.sync('firstName')} />
    }
}
```


Remember that we mentioned this sentence to the concent before round 2 started to compare: ** Can we not only trace the complete link of data modification, but also include the source of triggering data modification **, what does it mean, because every concent component The `ctx` all have a unique id` ccUniqueKey` to identify the current component instance, it is automatically generated according to `{className} _ {randomTag} _ {seq}`, that is, the class name (not provided is the component type `$$ CClass`, `$$CCHook`) plus random tags and self-increasing serial numbers, if you want to track and modify the source ui, you can manually maintain the `tag` and` ccClassKey`, and then cooperate with [concent- plugin-redux-devtool](https://github.com/concentjs/concent-plugin-redux-devtool/actions/new) will accomplish our goal.

```js
function FnUI(){
    const {sync, state, ccUniqueKey} = useConcent({module:'login', tag:'xxx'}, 'FnUI');
    // tag can be added or not added,
    // Without tag, ccUniqueKey looks like: FnUI_xtst4x_1
    // Tag added, ccUniqueKey looks like: FnUI_xxx_1
}

@register({module:'login', tag:'yyy'}, 'ClsUI')
class ClsUI extends React.Component{...}
```

After accessing `concent-plugin-redux-devtool`, you can see that any action modification Action will contain a field` ccUniqueKey`.

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/6.png)

### Review and summary
In this round, we made a comprehensive comparison of data modification methods, so that developers can understand from the perspective of `concent`, all aspects of the developer's coding experience to make great efforts.

Regarding the state update method, compared with `redux`, when all our action flows are minimized, there is no action-> reducer such a link, and it does not matter whether the stored function or the side effect function is distinguished (` rematch`, `dva` etc. Concept), it is more convenient and clearer to give these concepts to the `js` syntax itself. If you need pure functions, just write` export function`, and if you need side-effect functions, write `export async function`.

In contrast to `mobx`, everything is a basic function that can be disassembled in any combination. Without` this`, it is completely oriented to FP, giving an input `expected` output`. This way is also more friendly to the test container.

State modification | concent | mbox | redux
  -|-|-|-
  Based on the principle of immutability | Yes | No | Yes
  Shortest link | Yes | Yes | No
  ui source traceable | Yes | No | No
  Without this | Yes | No | Yes
  Atomic split & merge commit | Yes(based on lazy) | Yes(based on transaction) | No

## round 3 - Dependency collection
This round is a very heavy part. Dependency collection allows ui rendering to keep the minimum range of updates, that is, accurate updates, so `vue` will outperform` react` in certain tests. When we plug in the dependent collection After the wings, see what more interesting things will happen.

Before we start talking about `dependency collection`, let ’s review the original rendering mechanism of` react`. When a certain component changes state, if its custom component is not manually maintained by `shouldComponentUpdate`, it will always start from All rendering is done up and down, and the `cconnect` interface of` redux` takes over the `shouldComponentUpdate` behavior. When an action triggers an action modification, all connected components will update the state and current status of the` mapStateToProps` from the previous moment. The state obtained by `mapStateToProps` is shallowly compared to decide whether to refresh the wrapped subcomponents.

In the era of hooks, `React.memo` is provided to prevent users from blocking such" plant-based "updates, but users need to pass as much as possible primitive data or unchanged references to` props`, otherwise `React.memo` The shallow comparison will return false.

But one problem with `redux` is that if a state is no longer in use at a certain moment in the view, it should not be rendered but rendered, and` mobx` is carried based on the minimal subscription to the data obtained by the ui at runtime The concept of subsets elegantly solves this problem, but `concent` is a step closer to hiding the collection behavior more elegantly. Users do n’t need to know the relevant terms and concepts such as observable. Depends on the value, and the next rendering should remove the dependency on the value behavior of a certain `stateKey`, this vue` is doing very well, in order to make` react` have a more elegant and comprehensive dependency collection mechanism , `Concent` also made a lot of efforts.

### redux version (not supported)
Solving dependency collection is not the original intention of the birth of `redux`, here we can only silently invite it to the candidate area to participate in the next round of contests.

### mobx version (observable, computed, useObserver)
Use decorators or `decorate` functions to mark attributes to be observed or calculated

```js
import { observable, action, computed } from "mobx";

const delay = (ms = 1000) => new Promise(r => setTimeout(r, ms));

class LoginStore {
  @observable firstName = "";

  @observable lastName = "";

  @computed
  get fullName(){
    return `${this.firstName}_${this.lastName}`
  }

  @computed
  get nickName(){
    return `${this.firstName}>>nicknick`
  }

  @computed
  get anotherNickName(){
    return `${this.nickName}_another`
  }
}

export default new LoginStore();
```

When using the observation status or settlement result in ui, there is a dependency

- Depends only on the calculation result, component-like writing

```jsx
@inject("store")
@observer
class LoginCls extends Component {
  state = {show:true};
  toggle = ()=> this.setState({show:!this.state.show})
  render() {
    const login = this.props.store.login;
    return (
      <>
        <h1>Cls Small Comp</h1>
        <button onClick={this.toggle}>toggle</button>
        {this.state.show ? <div> fullName:{login.fullName}</div>: ""}
      </>
    )
  }
}
```

- Depends only on the calculation result, function component writing

```jsx
import { useObserver } from "mobx-react";

// When show is true, the current component reads fullName,
// fullName is calculated from firstName and lastName
// so its dependence is firstName, lastName
// when show is false, the current component has no dependencies
export const LoginFnSmall = React.memo((props) => {
  const [show, setShow] = React.useState(true);
  const toggle = () => setShow(!show);
  const { login } = store;

  return useObserver(() => {
    return (
      <>
        <h1>Fn Small Comp</h1>
        <button onClick={toggle}>toggle</button>
        {show ? <div> fullName:{login.fullName}</div>: ""}
      </>
    )
  });
});
```

There is no difference between relying on state and relying on calculation results, because the relevant results from `this.props.login` at runtime produce ui's dependence on data.

**[View mobx example](https://codesandbox.io/s/mobx-login-dx4wl)**

### concent(state,moduleComputed)
No decorator is needed to mark the observation properties and calculation results, just ordinary `json` objects and functions, which are automatically converted into` Proxy` objects at runtime.

计算结果依赖
```js
// code in models/login/computed.js
// n: newState, o: oldState, f: fnCtx

// The dependency of fullName is firstName lastName
export function fullName(n, o, f){
  return `${n.firstName}_${n.lastName}`;
}

// The dependency of nickName is firstName
export function nickName(n, o, f){
  return `${n.firstName}>>nicknick`
}

// anotherNickName makes a second calculation based on the cached result of nickName,
// and the dependency of nickName is firstName
// So the dependency of anotherNickName is firstName, 
// please note that this function needs to be placed under nickName
export function anotherNickName(n, o, f){
  return `${f.cuVal.nickName}_another`;
}
```

- Depends only on the calculation result, component-like writing

```jsx
@register({ module: "login" })
class _LoginClsSmall extends React.Component {
  state = {show:true};
  render() {
    const { state, moduleComputed: mcu, syncBool } = this.ctx;

    // When show is true, the instance's dependency is firstName + lastName
    // When false, there is no dependency
    return (
      <>
        <h1>Fn Small Comp</h1>
        <button onClick={syncBool("show")}>toggle</button>
        {state.show ? <div> fullName:{mcu.fullName}</div> : ""}
      </>
    );
  }
}
```

- Depends only on the calculation result, function component writing
```jsx
export const LoginFnSmall = React.memo(props => {
  const { state, moduleComputed: mcu, syncBool } = useConcent({
    module: "login",
    state: { show: true }
  });

  return (
    <>
      <h1>Fn Small Comp</h1>
      <button onClick={syncBool("show")}>toggle</button>
      {state.show ? <div> fullName:{mcu.fullName}</div> : ""}
    </>
  );
});
```

As with `mobx`, there is no difference between having a dependency on the state and a calculation result. Obtaining the relevant results from ctx.state at runtime creates the dependency of ui on the data. Every time you render` concent` The latest dependencies of the current instance are collected dynamically, and the disappeared dependencies are removed during the instance `didUpdate` phase.

- Life cycle dependence

The `concent` architecture unifies the life cycle functions of class components and function components, so when a state is changed, the life cycle functions that depend on it will be triggered, and support the logic shared by classes and functions

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/7.png)

```js
export const setupSm = ctx=>{
  // When the firstName changes, the component will be triggered after rendering
  ctx.effect(()=>{
    console.log('fisrtName changed', ctx.state.fisrtName);
  }, ['firstName'])
}

// Used in class components
export const LoginFnSmall = React.memo(props => {
  console.log('Fn Comp ' + props.tag);
  const { state, moduleComputed: mcu, sync } = useConcent({
    module: "login",setup: setupSm, state: { show: true }
  });
  //...
}

// Used in function components
@register({ module: "login", setup:setupSm })
class _LoginClsSmall extends React.Component {...}
```

**[View concent example](https://codesandbox.io/s/concent-login-ylbl4)**

**[Read more about ctx.effect](https://juejin.im/post/5deb43256fb9a0166316c3e9)**

### Review and summary
In the round of dependency collection, the dependent collection form of `concent` and the component expression form are very different from` mobx`. There is no other extra API involved in the entire dependency collection process, and `mbox` needs to use` computed` Modify the getter field. In the function component, you need to use the `useObserver` package status to return to the UI.` Concent` pays more attention to all functions. The keyword `this` is eliminated in the process of organizing the calculation code. The` fnCtx` function context is used to pass the Calculate the results, while explicitly distinguishing the container objects of `state` and` computed`.

Dependent collection | concent | mbox | redux
  -|-|-|-
  Support runtime collection of dependencies | Yes | Yes | No
  Precise rendering | Yes | Yes | No
  Without this | Yes | No | No
  Only one API is needed | Yes | No | No


## round 4 - Derived data
Remember the slogan of `mobx`? Any content that can be derived from the application state should be derived, revealing a problem that does exist and we cannot escape. Most application states are accompanied by a calculation process before being used by ui, and the calculation result is called Derived data.

We all know that this concept has been built into `vue`, which exposes an option` computed` to process the calculation process and cache derived data. `React` does not have this concept, and` redux` does not provide this capability. However, the open middleware mechanism of `redux` allows the community to find an entry point to support this capability, so here the calculation we have mentioned for` redux` has become the de facto popular standard library `reslect`.

Both `mobx` and` concent` have their own calculation support. We have demonstrated the derived data codes of `mobx` and` concent` in the ** Dependency Collection ** round above, so this round only writes derivatives for `redux` Sample data

### redux(reselect)
Redux recently released the `v7` version, which exposes two APIs,` useDispatch` and `useSelector`. The usage is completely equivalent to the previous` mapStateToState` and `mapDispatchToProps`. In our example, we will use both class components and function components. come out.

定义selector
```js
import { createSelector } from "reselect";

// getter, only used to get the value, does not participate in the calculation
const getFirstName = state => state.login.firstName;
const getLastName = state => state.login.lastName;

// selector，Equivalent to computed, manually import the calculation dependencies
export const selectFullName = createSelector(
  [getFirstName, getLastName],
  (firstName, lastName) => `${firstName}_${lastName}`
);

export const selectNickName = createSelector(
  [getFirstName],
  (firstName) => `${firstName}>>nicknick`
);

export const selectAnotherNickName = createSelector(
  [selectNickName],
  (nickname) => `${nickname}_another`
);

```

Class component gets selector

```js
import React from "react";
import { connect } from "react-redux";
import * as loginAction from "models/login/action";
import {
  selectFullName,
  selectNickName,
  selectAnotherNickName
} from "models/login/selector";

@connect(
  state => ({
    firstName: state.login.firstName,
    lastName: state.login.lastName,
    fullName: selectFullName(state),
    nickName: selectNickName(state),
    anotherNickName: selectAnotherNickName(state),
  }), // mapStateToProps
  dispatch => ({
    // mapDispatchToProps
    changeFirstName: e =>
      dispatch(loginAction.changeFirstName(e.target.value)),
    asyncChangeFirstName: e =>
      dispatch(loginAction.asyncChangeFirstName(e.target.value)),
    changeLastName: e => dispatch(loginAction.changeLastName(e.target.value))
  })
)
class Counter extends React.Component {
  render() {
    const {
      firstName,
      lastName,
      fullName,
      nickName,
      anotherNickName,
      changeFirstName,
      asyncChangeFirstName,
      changeLastName
    } = this.props;
    return 'ui ...'
  }
}

export default Counter;
```

Function component gets selector

```js
import * as React from "react";
import { useSelector, useDispatch } from "react-redux";
import * as loginAction from "models/login/action";
import {
  selectFullName,
  selectNickName,
  selectAnotherNickName
} from "models/login/selector";

const Counter = () => {
  const { firstName, lastName } = useSelector(state => state.login);
  const fullName = useSelector(selectFullName);
  const nickName = useSelector(selectNickName);
  const anotherNickName = useSelector(selectAnotherNickName);
  const dispatch = useDispatch();
  const changeFirstName = (e) => dispatch(loginAction.changeFirstName(e.target.value));
  const asyncChangeFirstName = (e) => dispatch(loginAction.asyncChangeFirstName(e.target.value));
  const changeLastName = (e) => dispatch(loginAction.changeLastName(e.target.value));

  return 'ui...'
  );
};

export default Counter;
```

**[Online example of redux derivative data](https://codesandbox.io/s/redux-login-6uqg8)**

### mobx (computed decorator)
See the example code above dependent on collection, no longer restated here.

### concent (directly obtained by moduleComputed)
See the example code above dependent on collection, no longer restated here.

### Review and summary
Compared to `mobx`, which can be obtained directly from` this.pops.someStore`, `concent` can be obtained directly from` ctx.moduleComputed`. There is an additional process of manually maintaining calculation dependencies or mapping selection results. The way that developers are more willing to use this result is clear at a glance.

   Derived data | concent | mbox | redux(reselect)
  -|-|-|-
  Automatically maintain dependencies between calculation results | Yes | Yes | No
  Collect dependencies when triggering to read calculation results | Yes | Yes | No
  Calculation function without this | Yes | No | Yes

## round 5 - Combat TodoMvc
The four rounds above combined a live code example, summarizing the characteristics and coding styles of the three frameworks. I believe that readers expect to have a code example that is closer to the production environment to see the difference. Then let us finally take "TodoMvc" comes to an end to this feature competition. I hope you can learn more about and experience `concent` and start the react programming journey of **immutable** & **dependent collection**.

### redux-todo-mvc
**[View redux-todo-mvc demo](https://codesandbox.io/s/todo-mvc-redux-49kg0)**

action related

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/8.png)

reducer related

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/9.png)
  
computed related

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/10.png)

### mobx-todo-mvc
**[View mobx-todo-mvc demo](https://codesandbox.io/s/todo-mvc-mbox-k42q1)**

action related

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/11.png)

computed related

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/12.png)
  
### concent-todo-mvc
**[View concent-todo-mvc demo](https://codesandbox.io/s/todoapp-react-concent-fvgvc)**

reducer related

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/13.png)

computed related

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/14.png)
  
  
 ## end
Finally, let us end this article with a [minimal version of the concent application](https://codesandbox.io/s/concent-watch-7j0mw). Will you choose concent as your react development weapon in the future?
 
```jsx
import React from "react";
import "./styles.css";
import { run, useConcent, defWatch } from 'concent';

run({
  login:{
    state:{
      name:'c2',
      addr:'bj',
      info:{
        sex: '1',
        grade: '19',
      }
    },
    reducer:{
      selectSex(sex, moduleState){
        const info = moduleState.info;
        info.sex = sex;
        return {info};
      }
    },
    computed: {
      funnyName(newState){
        // The dependency corresponding to the collected funnyName is name
        return `${newState.name}_${Date.now()}`
      },
      otherFunnyName(newState, oldState, fnCtx){
        // Get the calculation result of funnyName and newState.addr as input to calculate again
        // So the dependency corresponding to otherFunnyName collected here is name addr
        return `${fnCtx.cuVal.funnyName}_${newState.addr}`
      }
    },
    watch:{
      // watchKey name and stateKey have the same name, and watch name changes by default
      name(newState, oldState){
        console.log(`name changed from ${newState.name} to ${oldState.name}`);
      },
      // The values ​​of addr and info are read from newState,
      // the current watch function depends on addr and info,
      // when any one of them changes, this watch function will be triggered
      addrOrInfoChanged: defWatch((newState, oldState, fnCtx)=>{
        const {addr, info} = newState;

        if(fnCtx.isFirstCall)return;// Just to collect dependencies, do not execute logic

        console.log(`addr is${addr}, info is${JSON.stringify(info)}`);
      }, {immediate:true})
    }
  }
})

function UI(){
  console.log('UI with state value');
  const {state, sync, dispatch} = useConcent('login');
  return (
    <div>
      name:<input value={state.name} onChange={sync('name')} />
      addr:<input value={state.addr} onChange={sync('addr')} />
      <br />
      info.sex:<input value={state.info.sex} onChange={sync('info.sex')} />
      info.grade:<input value={state.info.grade} onChange={sync('info.grade')} />
      <br />
      <select value={state.info.sex} onChange={(e)=>dispatch('selectSex', e.target.value)}>
        <option value="male">male</option>
        <option value="female">female</option>
      </select>
    </div>
  );
}

function UI2(){
  console.log('UI2 with comptued value');
  const {state, moduleComputed, syncBool} = useConcent({module:'login', state:{show:true}});
  return (
    <div>
      {/* 
        When show is true, the dependency of the current component 
        is the dependency name corresponding to funnyName 
      */}
      {state.show? <span>dep is name: {moduleComputed.funnyName}</span> : 'UI2 no deps now'}
      <br/><button onClick={syncBool('show')}>toggle show</button>
    </div>
  );
}

function UI3(){
  console.log('UI3 with comptued value');
  const {state, moduleComputed, syncBool} = useConcent({module:'login', state:{show:true}});
  return (
    <div>
      {/* 
        When show is true, the dependency of the current component 
        is the dependency corresponding to funnyName name addr 
      */}
      {state.show? <span>dep is name,addr: {moduleComputed.otherFunnyName}</span> : 'UI3 no deps now'}
      <br/><button onClick={syncBool('show')}>toggle show</button>
    </div>
  );
}

export default function App() {
  return (
    <div className="App">
      <h3>try click toggle btn and open console to see render log</h3>
      <UI />
      <UI />
      <UI2 />
      <UI3 />
    </div>
  );
}
```
 
 
[❤ star me if you like concent ^_^](https://github.com/concentjs/concent)

[Edit on CodeSandbox](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/15.png)
![https://codesandbox.io/s/concent-guide-xvcej](https://user-gold-cdn.xitu.io/2019/11/17/16e79b2dee1e3c4d?w=302&h=62&f=png&s=9408)

[Edit on StackBlitz](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/16.png)
![https://stackblitz.com/edit/cc-multi-ways-to-wirte-code](https://user-gold-cdn.xitu.io/2019/11/17/16e79b30771f4fd4?w=302&h=61&f=png&s=10028)

If you have any questions about concent, you can scan the code and add group consultation, will try to answer the questions and help you understand more.

![](https://raw.githubusercontent.com/fantasticsoul/assets/master/article-img/rmc-comparison/17.png)