State of the state

UI = fn(state)

The above formula shows that, given the same state, FN will always generate a consistent UI.

In the React world, we need to add props:

VirtualDOM = fn(props, state)

As we can see from the figure, on the UI, we can perform interface actions (button pressing, keyboard typing, etc.). These interface actions are called actions. The important point here is that the data flow is UI => Action => state. The UI does not modify state directly, but changes state by sending out actions.

The advantage of this is that the UI layer is only responsible for synchronous interface rendering.

When State changes, it notifies all of its observers. The UI is only one of the most important observers, and there are often others.

The other observer is informed of what we call Side Effects. After performing side Effect, the observer itself will perform action distribution to update state, which is essentially different from state.

MobX core concepts

import { observable } from 'mobx';

let cart = observable({
    itemCount: 0.modified: new Date()});Copy the code

An Observable is the observed state. It’s reactive.

We declare the observed, and then we need to declare the observer to make sense.

import { observable, autorun } from 'mobx';

autorun((a)= > {
    console.log(`The Cart contains ${cart.itemCount} item(s).`);
}); // The Cart containers 0 item(s)


cart.itemCount++; // The Cart containers 1 item(s)
Copy the code

Autorun is an observer that automatically observes observable variables in a function and executes the function if the variable changes. (In particular, it is executed immediately after the function is registered, regardless of whether the variable has changed. ItemCount changes once and Autorun executes twice.

Similar to Redux’s thinking, modifying state directly is evil and ultimately leads to program clutter. In MOBx, the cart.itemcount ++ operation above, we need to put it in the action.

import { observable, autorun, action } from 'mobx';

const incrementCount = action((a)= > {
    cart.itemCount++;
})

incrementCount();
Copy the code

In Mobx, Side Effects are also called Reactions. The difference between reaction and action is that action is used to change the state, while reaction is executed after the state changes.

Action => State => reaction Indicates that the state of the action changes.

Observables , Actions , Reactions

Observable () converts an Object, array, or map into an Observable entity. For JavaScript primitives (number, String, Boolean, null, undefined), function functions, or class types, this does not work and may even raise an exception.

For these special types, MobX provides an Observable.box () API that can be used as follows:

const count = observable.box(20);
console.log(`Count is ${count.get()}`); // get()
count.set(25); // set()
Copy the code

The API scenarios for Observables are as follows:

The data type API
object observable.object({})
arrays observable.array([])
maps observable.map(value)
primitives, functions, class-instances observable.box(value)

MobX also has computed functionality similar to Vuex, which we call derivations in MobX. It’s easy to use, just declare the get attribute on the object:

const cart = observable.object({
    items: [].modified: new Date(),
    
    get description() {
        switch (this.items.length) {
            case 0:
                return 'no items in the cart';
            default:
                return `The ${this.items.length} items in the cart`; }}})Copy the code

In ES6 we can use our MobX in the form of decorators:

class Cart {
    @observable.shallow items = []; // => observable.array([], { deep: false })
    @observable modified = new Date(a); @computed get description() {switch (this.items.length) {
            case 0:
                return 'no items in the cart';
            default:
                return `The ${this.items.length} items in the cart`;
        }
    }
    @action
    addItem = (a)= > {
        this.items.push('new one'); }}Copy the code

MobX has three reactions: Autorun (), reaction(), and When ().

autorun()

import { observable, action, autorun } from 'mobx';

class Cart {
    @observable modified = new Date(a); @observable.shallow items = [];constructor() {
        this.cancelAutorun = autorun((a)= > {
            console.log(`Items in Cart: The ${this.items.length}`); // 1. Console output: Items in Cart: 0
        });
    }

    @action
    addItem(name, quantity) {
        this.items.push({ name, quantity });
        this.modified = new Date();
    }
}

const cart = new Cart();
cart.addItem('Power Cable'.1); // 2. Console output: Items in Cart: 1
cart.addItem('Shoes'.1); // 3. Console output: Items in Cart: 2

cart.cancelAutorun();

cart.addItem('T Shirt'.1); // The console does not output
Copy the code

autorun(effect-function): disposer-function

effect-function: (data) => {}

As you can see from the signature of Autorun (), after executing Autorun (), it returns to the disposer function of effe-function, which is used to stop the listener of Autorun (), Similar to what clearTimer(timer) does.

reaction()

reaction(tracker-function, effect-function): disposer-function

tracker-function: () => data, effect-function: (data) => {}

Reaction () has one more tracker-function function than Autorun (), which generates output data to effe-function based on the listening state. Only when this data changes will the effect-function be triggered to execute.

import { observable, action, reaction, toJS } from 'mobx';

class ITOffice {
    @observable members = []
    constructor() {
        reaction((a)= > {
            const femaleMember = this.members.find(x= > x.sex === 'female');
            return femaleMember;
        }, femaleMember => {
            console.log('Welcome new Member !!! ')
        })
    }
    @action addMember = (member) = > {
        this.members.push(member)
    }
}

const itoffice = new ITOffice();

itoffice.addMember({
    name: 'salon lee'.sex: 'male'
});

itoffice.addMember({
    name: 'little ming'.sex: 'male'
});

itoffice.addMember({
    name: 'lady gaga'.sex: 'female'
}); // 1. Console output: Welcome new Member!!
Copy the code

In the office above, Reaction monitored the arrival of new employees, but only if they were female. This differential control is achieved through tracker-function.

when()

when(predicate-function, effect-function): disposer-function predicate-function: () => boolean, effect-function: () = > {}

Like reaction(), when() has a prejudgment function, but when() returns a Boolean value of true/false. Effect-function is executed only if predicate-function returns true, and effect-function is executed only once. In other words, when() is a one-time side effect, and when this condition is true, it will take off from the action of this side effect, which means it calls the disposer-function.

There is another way to write when(), which is to use await when() and pass only the first predicate-function argument.

async () {
    await when(predicate-function);
    effect-function(); } / / < =when(predicate-function, effect-function)
Copy the code

MobX React

To use Mobx in React, we need to install the mobx-React library.

npm install mobx-react --save
Copy the code

And use the Observer to connect the React component to the MOBx state.

First create our shopping cart:

// CartStore.js
import { observer } from "mobx-react";

export default class Cart {
    @observer modified = new Date(a); @observer.shallow items = []; @action addItem = (name, quantity) {while (quantity > 0) {
            this.items.push(name)
            quantity--;
        }
        this.modified = new Date();
    }
}
Copy the code

Then inject the shopping cart state into the context via the Provider:

// index.js
import { Provider } from 'mobx-react';
import store from './CartStore'

ReactDOM.render(
    <Provider store={new store()} >
        <App />
    </Provider>.document.getElementById('root'));Copy the code

Then inject store into props by injecting it into other component files:

// app.js

import React from 'react';
import './App.css';

import { inject, observer } from 'mobx-react';

@inject('store')
@observer
class App extends React.Component {
  render() {
    const { store } = this.props;

    return (
      <React.Fragment>
        {store.items && store.items.map((item, idx) => {
          return <p key={item + idx} >{item + idx}</p>
        })}
        <button onClick={()= >Store. AddItem ('shoes', 2)}> Add 2 pairs of shoes</button>
        <button onClick={()= >Store. AddItem ('tshirt', 1)}> Add 1 shirt</button>
      </React.Fragment>
    );
  }
}

export default App;
Copy the code

Store design

Congratulations on seeing the last chapter of the Beginner’s Guide. This article doesn’t cover much of MobX’s advanced apis and inner principles because.. The title is “Beginner’s Guide.” Why would we want to scare people with something so difficult, and you should be able to handle most normal development scenarios after you’ve read it. So don’t panic, this is your introduction to Mobx. Congratulations.

Finally, here’s how you should design your store when using Mobx as your state management solution. In fact, this is more individual or team style, and the pros and cons of the level of thinking.

There is no standard answer here, only for reference.

Step 1: Declarestate

class Hero {
    @observable name = 'Hero'; / / name
    @observable blood = 100; / / health
    @observable magic = 80; / / mana
    @observable level = 1; / / level

    constructor(name) {
        this.name = name; // Initialize the hero name}}Copy the code

Step 2: By your keystatederivedcomputed

class Hero {
    @observable name = 'Hero';
    @observable blood = 100;
    @observable magic = 80;
    @observable level = 1;

    @computed
    get isLowHP() { // Whether low blood volume
        return this.blood < 25;
    }
    @computed
    get isLowMC() { // Whether the mana is low
        return this.magic < 10;
    }
    @computed
    get fightLevel() { / / fighting capacity
        return this.blood * 0.8 + this.magic * 0.2 / this.level
    }

    constructor(name) {
        this.name = name; }}Copy the code

Step 3: Declareaction

class Hero {
    @observable name = 'Hero';
    @observable blood = 100;
    @observable magic = 80;
    @observable level = 1;

    @computed
    get isLowHP() {
        return this.blood < 25;
    }
    @computed
    get isLowMC() {
        return this.magic < 10;
    }
    @computed
    get fightLevel() {
        return this.blood * 0.8 + this.magic * 0.2 / this.level
    }

    @action.bound
    beAttack(num) { / / be attacked
        this.blood -= num;
    }

    @action.bound
    releaseMagic(num) { // Cast magic
        this.magic -= num;
    }

    @action.bound
    takePill() { / / to eat pills
        this.blood += 50;
        this.magic += 25;
    }

    constructor(name) {
        this.name = name; }}Copy the code