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 keystate
derivedcomputed
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