Reference links:

Juejin. Cn/post / 684490…

Mobx official documentation

preface

Mobx is a Transparently Functional Reactive Programming (TFRP) state management library that makes state management simple and scalable:

1. Basic concepts

  • Actions: Actions that change state values.
  • State: indicates the observed status value
  • Computed Value: A value calculated using pure function, based on state
  • Reactions: Reactions due to changes in State or computed value, mainly view UI rerendering

2. Basic usage

Installation:

npm install mobx --save
npm install mobx-react --save
Copy the code

Create an instance of mobx Store:

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

 class Store{
    @observable cities = [];

    @computed
    get total() {
        return this.cities.length;
    }

    @action.bound
    clearCities() {

        this.cities = [];

    }

    @action.bound
    add(values) {
        this.cities = values; }}export new Store()
Copy the code
  • @obserbaleDefines a state value
  • @computedEvaluate a property, representing a derived value calculated from existing data, using a function as an attribute (the return value of a function)
  • @action.boundor@acitonXXX: specifies the method to update the state, which contains the logic to update the state. In this way, the state can be changed directly through this. XXX

2.1 Actions with asynchronous operations

Mobx does not allow direct data updates of state in asynchronous operations such as promise. then in @action or async @aciton

Official explanation:

The action wrapper/decorator responds only to the currently running function, not to the functions (not included in the current function) called by the currently running function! This means that if there are setTimeout, PROMISE then, or async statements in the action, and some state changes in the callback function, those callback functions should also be wrapped in the action.

One possible solution would be for the part of the then callback that changes state for a Promise to also encapsulate an @aciton, so calling that ACiton instead of modifying it directly in the callback would solve the problem

The recommended action is to use the runInAction provided by Mobx to modify state:

Promise. Then:

class Store {
    @observable githubProjects = []
    @observable state = "pending" // "pending" / "done" / "error"

    @action
    fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        fetchGithubProjectsSomehow().then(
            projects= > {
                const filteredProjects = somePreprocessing(projects)
                // Put the "final" modification into an asynchronous action
                runInAction(() = > {
                    this.githubProjects = filteredProjects
                    this.state = "done"})},error= > {
                // Another end of the process :...
                runInAction(() = > {
                    this.state = "error"})})}}Copy the code

Async actions:

 @action
    async fetchProjects() {
        this.githubProjects = []
        this.state = "pending"
        try {
            const projects = await fetchGithubProjectsSomehow()
            const filteredProjects = somePreprocessing(projects)
            // after await, modify state again need action:
            runInAction(() => {
                this.state = "done"
                this.githubProjects = filteredProjects
            })
        } catch (error) {
            runInAction(() => {
                this.state = "error"}}})Copy the code

2.2 the difference between @action.bound and @aciton

Official explanation:

The Action decorator/function follows the standard binding rules in javascript. However, action.bound can be used to automatically bind actions to the target object (that is, the instance of the current Store).

Note: action.bound should not be used with arrow functions; The arrow function is already bound and cannot be rebound.

The following is an example:

Use MOBx for 2.3 pages

In the required page, the corresponding Store instance is introduced, and the @Observer of Mobx-React is used to monitor data, so as to realize the display and update of data and drive the update

Store.js

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

 class Store{
    @observable cities = ["Beijing"."Shanghai"];
     @observable name = "scw";

    @computed
    get total() {
        return this.cities.length;
    }

    @action.bound
    clearCities() {
        this.cities = [];

    }

    @action.bound
    add(values) {
        this.cities = values; }}export new Store()
Copy the code

Page components:

import { observer, inject } from "mobx-react"
import Store from "./Store"

@observer
class Page extends Component {
  componentDidMount() {
    Store.add("Nanjing")}render() {
    return (
        <div>
          {Store.name}
          {Store.ciies.map(item=><span>{item}</span>)}
        </div>)}}export default Page
Copy the code

2.3 Mobx global injection

Mobx does not enforce store injection globally as Redux does, as shown in 2.2. In Mobx, we can write a specific store for each specific page or function.

However, it can also inject single or multiple stores globally for use by global page components. Generally used for some public state or public method Settings

Unified management files for all stores:

You could introduce some common stores here, encapsulating them as objects

import City from "./city";

export default {
    city: new City()
};
Copy the code

Top-level component:

Here we introduce the mox-React Providr to inject the public Store globally:

import React from "react";
import {render} from "react-dom";
import {Provider} from "mobx-react";
import {Router, browserHistory, hashHistory} from "react-router";
import { RouterStore, syncHistoryWithStore } from "mobx-react-router";
import routes from "./routes";
import store from "./mobstore";

const routingStore = new RouterStore();
const history = syncHistoryWithStore(hashHistory, routingStore);

store.routing = routingStore;

render(
    <Provider {. store} >
        <Router history={history} routes={routes}/>
    </Provider>.document.querySelector(".container"));Copy the code

Component uses globally injected Store:

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



@inject("city") 
@observer
export default class CitySelector extends Component{ constructor(... props) { super(... props);this.state = {
            city: []
        };
    }
   onChange = (value) = > {this.props.city.add(value);
    };

    clearCities() {

        this.props.city.clearCities();

    }

    render() {

        let { cities, total } = this.props.city; .Copy the code

use@inject("xxx")To call the Store of the specified property name in the public Store object, at which point the component passesthis.props.xxxTo access the corresponding Store. That includes the data and the methods that are in there @Observable and @aciton

3. Redux vs Mobx

(1) Single store and multiple stores

The Store is where apps manage their data, and in Redux apps, we always put all our shared app data in one big store,

Mobx typically divides application state into modules, managed in separate stores.

(2) JavaScript objects and observables

Redux stores data as JavaScript native objects by default, while Mobx uses observables:

  • Redux needs to manually track all state object changes;
  • In Mobx, observables can be listened on and automatically triggered when they change;

(3) Immutable and Mutable

Redux state objects are usually Immutable. We cannot manipulate state objects directly, but always return a new state object based on the original state object, which makes it easy to return and apply the previous state.

In Mobx, you can update the status object directly with the new value.

(4) Mobx-react and react-redux

To connect the React and Redux applications, use the Provider and connect provided by Redux:

  • Provider: injects stores into React applications.
  • Connect: is responsible for injecting store state into the container component and passing the selected state as the container component props;

For Mobx, there are also two steps:

  • Provider: Use the Provider provided by Mox-React to inject all stores into the application.
  • Inject a particular Store into a component. Stores can pass state or action. An Observer is then used to ensure that the component can respond to observable changes in the store, i.e., store updates, and component view responsive updates.

Why Mobx was chosen

Less learning cost: Mobx basic knowledge and configuration is very simple, while Redux is more complicated with more procedures, which need to configure, create store, compile reducer and action. If asynchronous tasks are involved, additional code needs to be introduced to redux-Thunk or Redux-saga. Mobx process is much simpler. And no additional asynchronous processing libraries are required;

Reasons for not choosing Mobx

Too much freedom: Mobx provides very few conventions and template code, which leads to very free development code writing. If you do not make some conventions, it is easy to cause the team code style to be inconsistent. Therefore, when there are many team members, you do need to add some conventions. Because of this, Mobx is scalable and maintainable