1. Objective of this paper

Here’s what you might learn from reading this article:

  • Configure the decorator environment
  • Understand the concept of responsive programming
  • Proper use of mobx key apis to achieve the goal of maintaining application state

2. What are modifiers?

A Decorator is a syntax for implementing class and class member annotations in the declaration phase.

To put it bluntly, a Decorator is a class that adds or modifies variables and methods.

2.1 Do MODIfiers need to be used?

One more thing to worry about when starting moBox is configuring the environment to enable ES7’s decorator syntax, although you can skip this section if you don’t need a decorator.

If you are new to MobX, this is recommended. The official MobX documentation also states that it is not necessary to use decorators. If someone says you must use a decorator in MobX, that is not true.

import { decorate, observable } from "mobx";

class Todo {
    id = Math.random();
    title = "";
    finished = false;
}
decorate(Todo, {
    title: observable,
    finished: observable
})
Copy the code

2.2 Using and not using modifiers

Do not use modifiers as follows:

import React, {Componnet} from 'react';
import {observe} from 'mobx-react'; Class Test extends Componnet {... }export default observe(Test);
Copy the code

Use the following modifiers:

import React, {Componnet} from 'react';
import {observe} from 'mobx-react'; @observe class Test extends Componnet{... }export default Test;
Copy the code

In the above code, defining a class through observer(App) is the same as decorating a component with @Observer Class App.

So what happens when multiple modifiers are combined on a component? .

Do not use modifiers as follows:

import React, {Componnet} from 'react';
import {observe, inject} from 'mobx-react';
import {compose} from 'recompose'; Class Test extends Componnet {render() { const {foo} = this.props; }}export default compose(
  observe,
  inject('foo')
)(Test)
Copy the code

Use the following modifiers:

import React, {Componnet} from 'react';
import {observe, inject} from 'mobx-react'; // Use the decorator @inject('foo') @observe
class Test extends Componnet {
  render() { const {foo} = this.props; }}export default Test;
Copy the code

If there is no modifier, recompose should be added to the Test by composing multiple modifiers. If modifiers are added to the Test, they can be added directly before the class Test, such as @inject(‘foo’) @observe, By comparison, you can see that the way to decorate with a modifier is much more concise and understandable. Read mobx’s Chinese documentation for more details

2.3 Advantages and disadvantages of using modifiers

Advantages of using modifiers:

  • Boilerplate minimization, declarative code.
  • Easy to use and read. Most MobX users use it.

Disadvantages of using modifiers:

  • ES. Next Phase 2 feature.
  • Setup and compilation is required, currently only supported by the Babel/Typescript compiler.

Create-react-app + Mobx decorator

Create-react-app does not yet have built-in decorator support, so this summary addresses this issue.

3.1. Install react-app-rewire related

Install the react – app – rewired

npm install react-app-rewired --save-dev
Copy the code

Modify the startup configuration in package.json

/* package.json */
"scripts": {
    "start": "node scripts/start.js"."build": "node scripts/build.js"."test": "node scripts/test.js"
  },
Copy the code

Create a config-overrides. Js file in the root directory of the project to modify the default configuration as follows:

+-- your-project
|   +-- config-overrides.js
|   +-- node_modules
|   +-- package.json
|   +-- public
|   +-- README.md
|   +-- src
Copy the code

3.2 eject installation

After creating a project, install eject to generate a custom configuration file because there is no traditional webpack.config file.

npm i eject
Copy the code

3.3. Install bable related

Specific configuration reference link

npm install --save-dev @babel/core npm install --save-dev @babel/plugin-proposal-class-properties npm install --save-dev  @babel/plugin-proposal-decoratorsCopy the code

After installing the above commands, package.json, if set in the package.json file, do not create a new.babelrc file again, otherwise you will get a duplicate error.

//package.json
"babel": {
    "plugins": [["@babel/plugin-proposal-decorators",
        {
          "legacy": true}], ["@babel/plugin-proposal-class-properties",
        {
          "loose": true}]],"presets": [
      "react-app"]}Copy the code

4. Test your knife to see if it works?

4.1. Implementation process

Install the above steps and you can restart the project

npm start
Copy the code

Let’s say we have a parent component, Father, and a Child component that writes the observed data, gets the data, sets the data, and resets the data. The parent component code is as follows:

import React, {Component} from 'react';
/ / introduce mobx
import {observable, computed, action} from "mobx";
// Introduce child components
import Child from "./Child.js";

class VM {
  @observable firstName = "";
  @observable lastName = "";

  @computed
  get fullName() {
    const {firstName, lastName} = this;
    if(! firstName && ! lastName) {return "Please input your name!";
    } else {
      return firstName + "" + lastName;
    }
  }

  @action.bound
  setValue(key, event) {
    this[key] = event.target.value;
  }

  @action.bound
  doReset() {
    this.firstName = "";
    this.lastName = ""; }}const vm = new VM();

export default class Father extends Component {

  render() {
    return (
     <Child vm={vm}/>)}}Copy the code

Give the child component a modifier @observer the child component code is as follows

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


@observer
class Upload extends Component {
    render(){
        // Deconstruct the data from the parent component
        const {vm} = this.props;
        return( <div> <h1>This is mobx-react! </h1> <p> First name:{" "} <textarea type="text" value={vm.firstName} onChange={e => vm.setValue("firstName", e)} /> </p> <p> Last name:{" "} <textarea type="text" value={vm.lastName} onChange={e => vm.setValue("lastName", e)} /> </p> <p>Full name: {vm.fullName}</p> <p> <button onClick={vm.doReset}>Reset</button> </p> </div> ) } }Copy the code

Effect of 4.2.

5. Mobx commonly used API

5.1. Observable data

observable

Observable: A method that allows changes in data to be observed

What data can be observed? JS basic data types, reference types, ordinary objects, class instances, arrays, and mappings

The following code:

const arr = observable(['a'.'b'.'c']);
const map = observable(new Map());
const obj = observable({
  a: 1.b: 1
});
Copy the code

observable.box

Observable. Box: Wrapper values, booleans, strings

The following code:

let num = observable.box(20);
let str = observable.box('hello');
let bool = observable.box(true);

// Use set() to set the value
num.set(50);

// get() gets the native value
console.log(num.get(), str, bool);
/ / 50
// ObservableValue? 1 {name: "ObservableValue@5", isPendingUnobservation: false, isBeingObserved: false, observers: Set(0), diffValue: 0,... }
// ObservableValue? 1 {name: "ObservableValue@6", isPendingUnobservation: false, isBeingObserved: false, observers: Set(0), diffValue: 0,... }

Copy the code

Use modifiers

Is it relatively cumbersome not to use modifiers? Always keep the type of the variable in mind, and with modifiers, there’s some internal conversion of variables, and it’s much more concise. The code is as follows:

class Store {
  @observable array = [];
  @observable obj = {};
  @observable map = new Map();

  @observable string = 'hello';
  @observable number=20;
  @observable bool=false;
}

Copy the code

5.2. Respond to observable data

Methods to observe changes in data: computed, Autorun, WHEN, and Reaction

Computed: Combine multiple observable data into one observable

Autorun: If the application is all observable data, and the application renders UI, writes to the cache, etc., are set to Autorun, we can write code that only deals with the data state, so as to achieve the goal of unified data management.

When: provides conditional execution logic, a variant of Autorun.

Reaction: Separate observable data declarations and make improvements to Autorun.

The four methods have their own characteristics and complement each other

5.3. Modify Observable Data (Action)

As mentioned before, there is still a problem to be solved, which is the performance problem. If there is a large number of data, every small change will trigger Autorun, as follows:

import {observable, isArrayLike, computed, action, runInAction, autorun, when, reaction} from "mobx";

class Store {
  @observable array = [];
  @observable obj = {};
  @observable map = new Map(a); @observable string ='hello';
  @observable number = 20;
  @observable bool = true;

  @computed get mixed() {
    return store.string + ':'+ store.number; }}let store = new Store();

reaction((a)= > [store.string, store.number,store.bool], arr => console.log(arr.join('+')));

store.string = 'word';
store.number = 25;
store.bool = true;

// Print the result
word+20+false
word+25+false
word+25+true
Copy the code

We can see from the above results that each change triggers reaction, so what can be done about it?

You can use action to solve this problem. Modify the code as follows:

import {observable, isArrayLike, computed, action, runInAction, autorun, when, reaction} from "mobx";

class Store {
  @observable array = [];
  @observable obj = {};
  @observable map = new Map(a); @observable string ='hello';
  @observable number = 20;
  @observable bool = false;

  @action bar() {
    store.string = 'word';
    store.number = 333;
    store.bool = true; }}let store = new Store();

reaction((a)= > [store.string, store.number, store.bool], arr => console.log(arr.join('+')));

store.bar();

// Print the result
word+333+true
Copy the code

Using action, we found that although three data changes were made, the Reaction method was called only once, resulting in performance optimization. Therefore, when there is a lot of data, it is recommended to use action to update the data.

6.mobx实现TodoList

The functions are as follows:

  • List display of Todo entries
  • Add Todo entry
  • Modification Status
  • Delete the Todo entry

First, create a TodoList folder and create a store.js file in the folder. This file will be used for data processing.

// store.js
import {observable, computed, action} from "mobx";

class Todo {
  id = Math.random();
  @observable title = ' ';
  @observable finished = false;

  constructor(title) {
    this.title = title;
  }

  @action.bound toggle() {
    this.finished = !this.finished; }}class Store {
  @observable todos = [];

  @action.bound createTodo(title) {
    this.todos.unshift(new Todo(title))
  }

  @action.bound removeTode(todo) {
    // Remove is not a native method, it is provided by Mobx
    this.todos.remove(todo);
  }

  @computed get left() {
    return this.todos.filter(item= > !item.finished).length;
  }
}

var store = new Store();


export default store;
Copy the code

Create a new todolist.js file

import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {observer, PropTypes as ObservablePropTypes} from 'mobx-react';
import TodoItem from "./TodoItem"; @observer class TodoList extends Component {static propTypes = {store: propTypes. Shape ({createTodo: PropTypes.func, todos: ObservablePropTypes.observableArrayOf(ObservablePropTypes.observableObject).isRequired }).isRequired }; state = { inputTile:""}; HandleSubmit = (e) => {// Form submission prevents the entire page from being submitted e.preventDefault();let {store} = this.props;
    let{inputTile} = this.state; store.createTodo(inputTile); // After creating a new entry, empty the input field this.setState({inputTile:""})}; handleChange = (e) => { var inputTile = e.target.value; this.setState({ inputTile }) };render() {
    let {inputTile} = this.state;
    let {store} = this.props;
    return <div className="todoList">
      <header>
        <form onSubmit={this.handleSubmit}>
          <input type="text"
                 onChange={this.handleChange}
                 value={inputTile}
                 placeholder="Where do you want to go?"
                 className="input"
          />
        </form>
      </header>
      <ul>
        {
          store.todos.map((item) => {
            return (
              <li key={item.id}>
                <TodoItem todo={item}/>
                <span onClick={()=>{store.removeTode(item)}}>删除</span>
              </li>
            )
          })
        }
      </ul>
      <footer>
        {store.left} 项 未完成
      </footer>
    </div>
  }
}

export default TodoList
Copy the code

Create a new todoitem.js file

import React, {Component} from 'react';
import {observer} from 'mobx-react';
import PropTypes from 'prop-types';

@observer
class TodoItem extends Component {
  // Attribute types passed from parent components are defined globally, with some restrictions
  static propTypes = {
    todo: PropTypes.shape({
      id: PropTypes.number.isRequired,
      title: PropTypes.string.isRequired,
      finished: PropTypes.bool.isRequired
    }).isRequired
  };

  handleClick = (e) = > {
    let {todo} = this.props;
    todo.toggle();
  }

  render() {
    // where Item is an object
    let {todo} = this.props;
    return (
      <div>
        <input
          type="checkbox"
          checked={todo.finished}
          onChange={this.handleClick}
        />
        <span className="title">{todo.title}</span>
      </div>
    )
  }
}

export default TodoItem;
Copy the code

Add TodoList components and store.js to your component


import React, {Component} from 'react';

import TodoList from ".. /.. /component/TodoList/TodoList";
import store from ".. /.. /store/store";

class PictureResources extends Component {
  render() {
    return (
      <TodoList store={store}/>
    )
  }
}

export default PictureResources
Copy the code

7. Use @Inject

In order to facilitate data management, we use @inject data. First, we find root components, such as app.js. We introduce Provider in Mox-React as the root container, and then we introduce all our data into APP components, creating a feeling of uniformly deploying data globally. If the sub-component needs store data, inject (@inject) as needed:

import React, {Component} from 'react';
// styles
import './scss/style.scss';
// store
import {Provider} from 'mobx-react';
import store from './store/store.js';

class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <div className="panel"></div>
      </Provider>); }}export default App;
Copy the code

To inject data into the desired subcomponent, note that “store” is injected in quotes as follows:

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

@inject("store")
@observer
class Upload extends Component {
  render() {
    return (
      <div></div>)}}export default Upload;
Copy the code