[mobx] mobx introduction

Time:2019-11-2

[mobx] mobx introduction

I. Introduction to mobx

First look at the official website:

Mobx is a war baptised library, which makes state management simple and extensible through transparent applying functional reactive programming (TFRP). The philosophy behind mobx is simple:
Anything from the application state should be automatically obtained.
It includes UI, data serialization, server communication, and so on.

The key points are:Mobx realizes simple, efficient and scalable state management through responsive programming

<imghttp://images.pingan8787.com/blog/mobx.png&quot” rel=”nofollow noreferrer”>http://images.pingan8787.com/…; width=”120px”/>

React and mobx relationship

React and mobx complement and cooperate with each other.

Official website:

React transforms the application state into a renderable component tree and renders it by providing a mechanism. Mobx provides a mechanism to store and update application status for react.

Mobx workflow

Here we will first understand the process of sorting, and then we will introduce each part in combination with the code.

[mobx] mobx introduction

Outline of this article

This article uses the version of mobx 5. It mainly introduces the use of mobx from the following aspects:

  1. Configuring the mobx development environment of webpack
  2. Introduction to mobx common API (main introduction andObservable dataRelated operations)
  3. Mobx simple example

[mobx] mobx introduction

2. Configure the mobx development environment of webpack

  • To install webpack and Babel dependency packages:
cnpm i webpack webpack-cli babel-core babel-preset-env babel-loader -D
  • To install mobx dependent packages:
cnpm i mobx-react -D
cnpm i babel-plugin-transform-class-properties -D 
cnpm i babel-plugin-transform-decorators-legacy -D 
  • Add configuration in webpack.config.js:

Be careful:transform-decorators-legacyIt must be the first one.

const path = require('path')

const config = {
    mode: 'development',
    entry: path.resolve(__dirname, 'src/index.js'),
    output: {
        path:  path.resolve(__dirname, 'dist'),
        filename: 'main.js'
    },
    module: {
        rules: [{
            test: /\.js$/,
            exclude: /node_modules/,
            use: {
                loader: 'babel-loader',
                options: {
                    presets: ['env'],
                    plugins: ['transform-decorators-legacy', 'transform-class-properties']
                }
            }
        }]
    },
    devtool: 'inline-source-map'
}

module.exports = config

III. introduction to mobx common API

1. Set observable data

1.1 (@)observable

observableIt is a way to make data changes observable. The bottom layer is to transform this attribute intogetter / setterTo achieve..

observableValues can be JS raw data type, reference type, normal object, class instance, array, and map.

Observable use

  • aboutJS original typeNumber/String/Boolean), using observable.box() Method settings:
const num = observable.box(99)
const str = observable.box('leo')
const bool = observable.box(true)

//Get the original value get ()
console.log(num.get(),str.get(),bool.get())   // 99 "leo" true

//Modify the original value set (params)
num.set(100);
str.set('pingan');
bool.set(false);
console.log(num.get(),str.get(),bool.get())  // 100 "pingan" false
  • aboutarrayobject typeUseobservable()Method settings:
const list = observable([1, 2, 4]);
list[2] = 3;
List. Push (5) // array methods can be called
console.log(list[0], list[1], list[2], list[3]) // 1 2 3 5

const obj = observable({a: '11', b: '22'})
console.log(obj.a, obj.b) // 11 22
obj.a = "leo";
console.log(obj.a, obj.b) // leo 22

It should be noted that:Should avoidSubscript boundary crossingRemove the value in the method array, such data will not be monitored by mobx:

const list = observable([1, 2, 4]);
// mistake
console.log(list[9]) // undefined

Therefore, in the actual development, we need to pay attention to the judgment of array length.

  • aboutmapping(map) type, usingobservable.map()Method settings:
const map = observable.map({ key: "value"});
map.set("key", "new value");
console.log(map.has('key'))  // true
map.delete("key");
console.log(map.has('key'))  // false

@Observable use

Mobx also offers the use of decorators@observableTo convert it to observable, you can use it on the fields and properties of the instance.

import {observable} from "mobx";

class Leo {
    @observable arr = [1];
    @observable obj = {};
    @observable map = new Map();
    @observable str = 'leo';
    @observable num = 100;
    @observable bool = false;
}
let leo = new Leo()
console.log(leo.arr[0]) // 1

Compared with the previous useobservable.box() Method pairJS original typeNumber/String/Boolean)Define, decorator@observableYou can define these types directly.

The reason is the decorator@observableFurther encapsulationobservable.box()

2. Respond to changes in observable data

2.1 (@)computed

Calculated value(computed values) are values that can be combined based on existing states or other calculated values. You can make the actual modifiable state as small as possible.

In addition, the calculated value isHighly optimizedSo use them as much as possible.

It can be simply understood as:It isAutomatically updated value when related status changes, multiple observable data can be combined into one observable data, and only in theAutomatically updated when used

Knowledge points: how to use

  • Usage 1: declarative creation
import {observable, computed} from "mobx";

class Money {
    @observable price = 0;
    @observable amount = 2;

    constructor(price = 1) {
        this.price = price;
    }

    @computed get total() {
        return this.price * this.amount;
    }
}
let m = new Money()

console.log(m.total) // 2
m.price = 10;
console.log(m.total) // 20
  • Usage 2: use decorate to introduce
import {decorate, observable, computed} from "mobx";

class Money {
    price = 0;
    amount = 2;
    constructor(price = 1) {
        this.price = price;
    }

    get total() {
        return this.price * this.amount;
    }
}
decorate(Money, {
    price: observable,
    amount: observable,
    total: computed
})

let m = new Money()

console.log(m.total) // 2
m.price = 10;
console.log(m.total) // 20
  • Usage 3: create with observable.object

observable.objectandextendObservableWill automaticallygetterAttribute derived intoCalculated attribute, so this is enough:

import {observable} from "mobx";

const Money = observable.object({
    price: 0,
    amount: 1,
    get total() {
        return this.price * this.amount
    }
})

console.log(Money.total) // 0
Money.price = 10;
console.log(Money.total) // 10
  • Attention points

If any value that affects the calculated value changes, the calculated value will automatically change based on the status.

If the data used in the previous calculation has not changed, the calculation properties will not be rerun If a calculation property is not used by another calculation property or reaction, it will not be rerun In this case, it will be suspended.

Knowledge point: setter of computed

computedOfsetterCan’t be used to changeCalculate the value of the propertyIt’s used by its members to makecomputedChanges.

Here we usecomputedFor example, the first way to declare is similar to other ways:

import {observable, computed} from "mobx";

class Money {
    @observable price = 0;
    @observable amount = 2;

    constructor(price = 1) {
        this.price = price;
    }

    @computed get total() {
        return this.price * this.amount;
    }

    set total(n){
        this.price = n + 1
    }
}

let m = new Money()

console.log(m.total) // 2
m.price = 10;
console.log(m.total) // 20
m.total = 6;
console.log(m.total) // 14

As can be seen from the above implementation,set totalMethod to receive a parameternAct aspriceWe callm.totalAfter setting up a newpriceSom.totalThe value of also changes.

Be careful:
The setter must be defined after the geeter. Some typescript versions will think that two properties with the same name are declared.

Knowledge point: computed (expression) function

Generally, the change can be observed and the calculated value can be obtained by the following two methods:

  • Method 1: putcomputedAs a function call, use the.get()To get the current value of the calculation.
  • Method 2: useobserve(callback)To observe the change of value, the calculated value is.newValueUp.
import {observable, computed} from "mobx";

let leo = observable.box('hello');
let upperCaseName = computed(() => leo.get().toUpperCase())
let disposer = upperCaseName.observe(change => console.log(change.newValue))
leo.set('pingan')

More detailedcomputedParameters can be viewed in the document: calculated options.

Knowledge points: error handling

If the calculated value throws an exception during calculation, the exception will be caught, andThrow an exception while reading its value

Throw exceptionNo interruption of tracking, all calculated values can be recovered from the exception.

import {observable, computed} from "mobx";
let x = observable.box(10)
let y = observable.box(2)
let div = computed(() => {
    If (y.get() = = 0) throw new error ('y is 0 ')
    return x.get() / y.get()
})

div.get() // 5
y.set(0)  // ok
Div.get() // error reported, y is 0

y.set(5)
Div.get() // return to normal, return 2

Summary

Usage:

  • computed(() => expression)
  • computed(() => expression, (newValue) => void)
  • computed(() => expression, options)
  • @computed({equals: compareFn}) get classProperty() { return expression; }
  • @computed get classProperty() { return expression; }

There are various options to controlcomputedAct. Include:

  • equals: (value, value) => booleanThe comparison function used to overload the default detection rule Built in comparators are:comparer.identity, comparer.default, comparer.structural
  • requiresReaction: booleanWaiting to be tracked before recalculating derived propertiesobservablesChange in value;
  • get: () => value)Overload thegetter
  • set: (value) => voidOverload thesetter
  • keepAlive: booleanSet totrueTo automatically keep the calculated value active instead of pausing when there is no observer;

2.2 autorun

concept

autorunLiteral translation isAutomatic operationSo we need to know these two questions:

  • What runs automatically?

That is: automatically run the incomingautorunParameter function of.

import { observable, autorun } from 'mobx'
class Store {
    @observable str = 'leo';
    @observable num = 123;
}

let store = new Store()
autorun(() => {
    console.log(`${store.str}--${store.num}`)
})
// leo--123

It can be seen thatautorunAutomatically run once and outputleo--123Obviously this is not automatic.

  • How to trigger automatic operation?

When modifying any observable data in autorunAutomatic operation can be triggered.

//Next to the code above

store.str = 'pingan'

// leo--123
// pingan--123

Now you can see the console output these two logs to prove thatautorunIt has been executed twice.

Knowledge point: observe the data of computed

import { observable, autorun } from 'mobx'
class Store {
    @observable str = 'leo';
    @observable num = 123;

    @computed get all(){
        return `${store.str}--${store.num}`
    }
}

let store = new Store()
autorun(() => {
    console.log(store.all)
})
store.str = 'pingan'

// leo--123
// pingan--123

As you can see, this willcomputedThe value ofautorunThe same effect can be achieved by observing in, which is also commonly used in our actual development.

Knowledge points: difference between computed and autorun

Same point:

Are all expressions called in response;

Difference:

  • @computedUsed to generate a response that can be used by other observervalue
  • autorunNo new value is generated, butAchieve an effect(such as: printing logs, initiating network requests and other command side effects);
  • @computedIf a calculated value is no longer observed, mobx can automaticallyGarbage collectionAndautorunThe values in must be cleaned up manually.

Summary

autorunBy default, it will be executed once to get which observable data is referenced.

autorunThe role ofAfter the observable data is modifiedAutomatically perform actions that depend on observable data, this behavior has always been passed inautorunFunction.

2.3 when

Receive two function parameters, the first functionMust return a Boolean value based on observable data, when the Boolean value istrueThe second function is executed only once.

import { observable, when } from 'mobx'
class Leo {
    @observable str = 'leo';
    @observable num = 123;
    @observable bool = false;
}

let leo = new Leo()
when(() => leo.bool, () => {
    Console. Log ('This is true ')
})
leo.bool = true
//This is true

It can be seen that whenleo.boolSet uptrueIn the future,whenThe second method is implemented.

Be careful

  1. The first parameter must be based onObservable dataTo return the Boolean value of instead of the Boolean value of a normal variable.
  2. If the first parameter defaults totrueThenwhenThe function executes once by default.

2.4 reaction

Receive two function parameters, the first functionReference observable data and return an observable data, as an argument to the second function.

reaction When rendering for the first time, the first function will be executed onceIn this way, mobx will know which observable data has been referenced. The second function is then executed when the data is modified.

import { observable, reaction } from 'mobx'
class Leo {
    @observable str = 'leo';
    @observable num = 123;
    @observable bool = false;
}

let leo = new Leo()
reaction(() => [leo.str, leo.num], arr => {
    console.log(arr)
})
leo.str = 'pingan'
leo.num = 122
// ["pingan", 122]
// ["pingan", 122]

Here we will modify them one by oneleo.strandleo.numTwo variables, you will findreactionMethod is executedTwice, output at consoleTwiceResult["pingan", 122]Because of observable datastrandnum partIt was modified once.

Actual use scenario:

When we don’t get the data, we don’t need to execute the cache logic. When we get the data for the first time, we execute the cache logic.

2.5 summary

  • computedMultiple observable data can be combined into one observable data;
  • autorunIt can automatically track the referenced observable data and trigger when the data changes;
  • whenYou can set the time to trigger the change automatically. YesautorunA variant of;
  • reactionBy separating the observable data statement, theautorunMake improvements;

They have their own characteristics and complement each other, and can play an important role in the appropriate scene.

3. Modify observable data

In the previous section, we learned that when responding to observable data, we need to manually modify the value of observable data. This modification is realized by directly assigning values to variables. Although it is easy to understand, it will bring a more serious side effect, namelyEvery modificationWill triggerautorunperhapsreaction Run once。 In most cases, this high frequency trigger is completely unnecessary.

For example, users need to modify many N state variables for a single click of a view, but only one view update is enough.

To optimize this problem, mobx introducedaction

3.1 (@)action

actionIs the behavior of modifying any state, usingactionThe advantage is thatMerge multiple modifiable observable states into oneTo reduce triggeringautorunperhapsreactionThe number of times.

It can be understood asBatch operation, that is to say, one action contains multiple modifications of observable state. At this time, only one-time recalculation and reaction will be done after the end of the action.

actionThere are also two ways to use itdecorateHow to introduce.

import { observable, computed, reaction, action} from 'mobx'

class Store {
    @observable string = 'leo';
    @observable number = 123;
    @action bar(){
        this.string = 'pingan'
        this.number = 100
    }
}
let store = new Store()
reaction(() => [store.string, store.number], arr => {
    console.log(arr)
})
store.bar() // ["pingan", 100]

When we continue to revisestore.stringandstore.numberAfter two variables, runstore.bar()It will be found that the console value is output once["pingan", 100], which meansreactionOnly once.

Knowledge point: action.bound

in additionactionThere is also a special method of use:action.boundOften used as acallbackAnd the execution effect is the same:

import { observable, computed, reaction, action} from 'mobx'

class Store {
    @observable string = 'leo';
    @observable number = 123;
    @action.bound bar(){
        this.string = 'pingan'
        this.number = 100
    }
}
let store = new Store()
reaction(() => [store.string, store.number], arr => {
    console.log(arr)
})
let bar = store.bar;
function foo(fun){
    fun()
}
foo(bar) //["pingan", 100]

Knowledge point: runinaction (name? Thunk)

runInActionIs a simple utility function that takes code blocks and executes them in (asynchronous) actions. This is useful for creating and executing actions on the fly, such asAsynchronous processMedium.runInAction(f)yesaction(f)()Grammar sugar.

import { observable, computed, reaction, action} from 'mobx'
class Store {
    @observable string = 'leo';
    @observable number = 123;
    @action.bound bar(){
        this.string = 'pingan'
        this.number = 100
    }
}
let store = new Store()
reaction(() => [store.string, store.number], arr => {
    console.log(arr)
})
runInAction(() => {
    store.string = 'pingan'
    store.number = 100
})//["pingan", 100]

IV. simple example of mobx react

Here, take the simple counter as an example to realize the simple operation of click button and value accumulation, as shown in the figure:

[mobx] mobx introduction

In this case, we quotemobx-reactIt can be seen clearly thatmobx-reactBe asmobxandreactBefore the bridge.

It willreactComponents are transformed into responses to observable data, that is, therenderMethod packagingautorunMethod to automatically re render when the state changes.

For details, please refer to: https://www.npmjs.com/package.

Let’s start with our case:

1. Install dependency and configure webpack

Since the configuration is similar to that described in the second section, the configuration will be added based on the configuration in the second section.

First installationmobx-reactDependence:

cnpm i mobx-react -D

modify webpack.config.jsInpresetsAdd in configurationreactCome in:

//... omit others
- entry: path.resolve(__dirname, 'src/index.js'),
+ entry: path.resolve(__dirname, 'src/index.jsx'),
module: {
    rules: [{
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: {
            loader: 'babel-loader',
            options: {
-                 presets: ['env'],
+                 presets: ['env', 'react'],
                plugins: ['transform-decorators-legacy', 'transform-class-properties']
            }
        }
    }]
},

2. Initialize the react project

Here we initialize the simple framework of our project:

// index.jsx
import { observable, action} from 'mobx';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import {observer, PropTypes as observablePropTypes} from 'mobx-react'

class Store {
    
}

const store = new Store();

class Bar extends Component{

}

class Foo extends Component{
    
}

ReactDOM.render(<Foo />, document.querySelector("#root"))

These components correspond to our final page, as shown in the figure below:

[mobx] mobx introduction

2. Implement store class

StoreClass is used to store data.

class Store {
    @observable cache = { queue: [] }
    @action.bound refresh(){
        this.cache.queue.push(1)
    }
}

3. Implement bar and foo components

The implementation code is as follows:

@observer
class Bar extends Component{
    static propTypes = {
        queue: observablePropTypes.observableArray
    }
    render(){
        const queue = this.props.queue;
        return <span>{queue.length}</span>
    }
}

class Foo extends Component{
    static propTypes = {
        cache: observablePropTypes.observableObject
    }
    render(){
        const cache = this.props.cache;
        Return < div > < button onclick = {this. Props. Refresh} > Click + 1 < / button > current value: < bar queue = {cache. Queue} / >
    }
}

Note here:

  1. The array in the observable data type is not actually an array type. Here we need to use theobservablePropTypes.observableArrayTo declare its type, so does the object.
  2. @observerIn needChange the UI components to reference according to the data transformation, additional suggestionsThere are classes that use relevant dataAll quoted.
  3. In fact, we just need to rememberobserverMethod, allReactComponent useobserverEmbellishment isreact-mobxUsage.

4. Use foo components

Finally, we useFooComponent, you need to pass it two parameters, soBarComponents can be obtained and used:

ReactDOM.render(
    <Foo cache={store.cache} refresh={store.refresh}/>, 
    document.querySelector("#root")
)

Ending

For reference:

  • Official mobx documents
  • Introduction to mobx

About me

This article is first published in Pingan 8787 personal blog. If you need to reprint it, please keep your profile.

Author Ping An Wang
E-mail [email protected]
Blog www.pingan8787.com
WeChat pingan8787
Daily article recommendation https://github.com/pingan8787…
ES Brochure js.pingan8787.com

WeChat public address

[mobx] mobx introduction

Recommended Today

Deeply analyze the principle and practice of RSA key

1、 Preface After experiencing many dark moments in life, when you read this article, you will regret and even be angry: why didn’t you write this article earlier?! Your darkest moments include: 1. Your project needs to be connected with the bank, and the other party needs you to provide an encryption certificate. You have […]