preface

Recently when I was learning MobX, I felt that I could not clearly understand some details from the official website documents, so I finished reading the MobX Quick Start Guide recommended by the official website and wrote this article from the beginning, hoping to provide some help to those who want to learn MobX quickly. If there are any unclear points or mistakes in this article, please feel free to discuss them in the comments section.

This article starts from what MobX is, and then compares MobX and Redux to let friends know some advantages of MobX, and then formally enters the world of MobX to understand the following contents:

  • How does MobX work
  • What are the core concepts of MobX
  • How does MobX work with React

I’ll end with a simple demo to show you how to use MobX in your projects. Next, let’s get started

What is MobX

I believe that some state management libraries will be used in project development to make state management more simple and controllable. For example, Redux is commonly used in React project and Vuex is commonly used in Vue project. MobX, our main character, is also a state management tool that is simple and extensible.

To say it’s simple is to mention the MobX philosophy — anything that comes out of the application state should be automatically acquired. Maybe real projects often have such demand, state C is calculated according to the state A and state B, A/B is changed, so the state state C should also be recalculated, in MobX computed, it is used when the state A/B is changed, it will automatically help us to update the status of C, hey hey, isn’t it convenient?

Vuex’s getters can do this, too. Let’s wait and see what the advantages of MobX are.

MobX vs. Redux

Unidirectional data flow

One-way data flow: The Store manages all state changes and notifies the UI and other observers when state changes

Redux and MobX both use one-way data flows, but their implementation mechanisms are different. Redux relies on immutable state snapshots and reference comparisons between the two state snapshots to check for changes, whereas MobX thrives in mutable state and uses grains notification systems to track state changes.

Low development difficulty

The API of Redux follows the functional programming style, which is a little difficult for beginners with only object-oriented knowledge. MobX uses the responsive programming style with rich semantics, and matches the API syntax with more simple object-oriented knowledge, which greatly reduces the learning cost. Meanwhile, MobX’s integration degree is also slightly higher than Redux. Avoiding the need for developers to bring in many fragmented third-party libraries.

Because reducer of Redux is a pure function and no side effects can be added, middleware is the only place that can implement side effects in Redux. Therefore, the use of Redux often requires the addition of many third-party middleware, such as redux-Thunk to support asynchronous operations. MobX, on the other hand, is highly integrated, with asynchronous operations and so on out of the box, without adding additional three-party libraries.

Less development code

Redux has many concepts such as Reducer, actions, store, etc. Every time a state is added, these positions should be updated synchronously, and there is a lot of boilerplate code. Mobx simply updates in the Store and writes much less code than Redux.

Good rendering performance

ShouldComponentUpdate in React can avoid unnecessary rerendering and improve page performance, but it is not easy to implement this method if the data level is complex. MobX describes exactly which components need to be rerendered and which do not. By properly organizing the hierarchy of components and the location of data structures, it is easy to keep view rerendering to a minimum, which can affect page performance.

MobX looks pretty good, simple and easy to use, good performance, so why hesitate, then read on, let’s get into the world of MobX

How MobX works

Let’s take a look at the following model diagram. This model diagram describes the idea of one-way data flow, that is, the change of state will correspond to the change of page UI, and the operation of user events on the page UI will trigger the Action to update the state.

However, if you find a small problem, this model diagram does not show how to deal with side effects (such as network requests, logs, etc.), so let’s update the model diagram.

This time a change in the state of the model diagram should be notified not only to the UI but also to the side effect handler. The side effect handler will execute the side effect when it is notified, and then bring the change of the side effect to actions to update the state.

Ha-ha, do you think the UI and the side effects handler do the same thing? They both receive notification of the state and then change the state through Actions, so we can think of them as observers. They observe the state and when the state changes, they react accordingly.

Observers, for example, are astute about MobX. The state is observables, and their UI and side effects are observers. When the observables change, they send a message to announce them to observers, and they can do something about it through their actions to notify them of updates.

The above pictures show you how MobX works. Now let’s take a look at some of the core MobX concepts and put MobX to use.

Core concepts of MobX

Because this part is all conceptual content, we will use a simple way to quickly go over the core concepts and some commonly used API, etc., the following demo explains how to use, want to know more details of API partners can refer to the documentation

observables

  1. Concept: Create an observable that can track changes that occur to any of its properties.
  2. API:
  • observablesCan only convert objects, arrays, maps, etc
  • observable.box: Primitives (number, string, Boolean, null, undefined) or for class- instances (objects with prototypes)
  • extendObservable(target, object, decorators): It allows other properties to be mixed at run time and made observable. Observables. Map can also dynamically extend Observable Properties, but it can’t dynamically extend Actions and computed-properties.
  1. Decorator syntax
  • shallow: Observe only layer 1 data
  • ref: There is no need to observe attribute changes (attributes are read-only), and ref is used when references are frequently changed
  • structThe reaction is triggered every time an object is updated, but sometimes only the reference updates the actual property content, which is why structs exist. Structs do deep comparisons based on properties.

actions

  1. Concept: Accepts a function that modifies the state of observables and will be called when an operation is invoked.
  2. Asynchronous actions
  • runInAction: Because the updated observable (fn) === Action(fn) () must reside in the action function, async is the syntax sugar of the Generator function, and the operations behind await are placed in the callback function, Because it is no longer directly in the action function, we have the runInAction to cover it with a layer to ensure that the operations following the await are still in the action.
import { action, observable, configure, runInAction } from 'mobx';
configure({ enforceActions: 'always'});

class ShoppingCart {
    @observable asyncState = ' ';
    @observable.shallow items = [];
    @action async submit() {
        this.asyncState = 'pending';
        const response = await this.purchaseItems(this.items);
        runInAction(() = >{
            this.asyncState = 'completed';
        });
    }
    purchaseItems(items) {
        / *... * /
        return Promise.resolve({}); }}}Copy the code
  • flowIf you have many await messages, you have to use the runInAction frequently. The code is less elegant, so use flow instead
import { observable, flow, configure } from 'mobx';
configure({ enforceActions: 'strict' });
class AuthStore {
    @observable loginState = ' ';
    login = flow(function* (username, password) {
        this.loginState = 'pending';
        yield this.initializeEnvironment();
        this.loginState = 'initialized';
        yield this.serverLogin(username, password);
        this.loginState = 'completed';
        yield this.sendAnalytics();
        this.loginState = 'reported';
        yield this.delay(3000);
    });
} 
new AuthStore().login();
Copy the code
  1. Advantages:
  • Better readability: Encapsulating actions with actions is more semantic
  • Performance boost: It treats multiple changes as a single atomic transaction, which also reduces the noise of too many notifications
  1. Understand the benefits of Actions in depth
action = untracked(transaction(allowStateChanges(true,\<mutating-function>)))
Copy the code
  • untracked: Prevents tracking observed objects in the mutating function (also known as creating new Observables)
  • transaction: Batch notifications, forcing notifications on the same observed objects, and then distributing the smallest set of notifications at the end of the operation
  • allowStateChanges: This ensures that the state changes actually occur on the observables and that they will get new values

The benefits of combining actions in this way are:

  1. Reduce excessive notifications
  2. Increase efficiency by batching the smallest set of notifications
  3. Minimize side effects for observables that change multiple times in one action
  4. Actions make your code more semantically and readable by wrapping details

Derivations

Anything that comes from a state and doesn’t have any further interaction is derivative. There are two main kinds of derivatives, computed and observers

computed

  1. Concept: a value that can be derived from an existing state or other computed value.
  2. Advantages: It caches the last computed value, recalculates only if the dependency changes, and does not trigger notification if the computed result is the same as the previously cached result. And no one uses it and it gets picked up by the garbage collection system.
  3. Decorator syntax
  • Struct: diff is not used to compare reference but to compare content in depth

observers(reactions)

  1. The concept: Observer, also known as Reactions, includes a side effect handler and UI. These reactions are reactions to changes in state.
  2. type
  • autorun: A dependency is executed automatically when it changes and can be deregistered using its return value function
  • reaction: Precisely controls some dependency changes before responding to conditions
    reaction(tracker-function.effect-function) :disposer-function
    Copy the code
    • Parameters:
      • tracker-function() => data, tracking all observables, which will be executed whenever any Observables change. It should return a value to compare it to the last tracker-function that was run. If the two values are different, the effect-function is fired.
      • effect-function(data) => {}, any Observable used in effect is not tracked
      • disposer-function: is the reaction return value function, which can be called to destroy the reaction
  • when: Effect -function is executed only when conditions are met and side effects are automatically handled after conditions are met. It is a one-time side effect
    when(predicate-function.effect-function) :disposer-function
    Copy the code
    • Parameters:
      • predicate-function: () => boolean
      • effect-function: () = > {}
  1. Choose appropriate observers

Determine if the side effect needs to be executed more than once, if not just once, use when; If multiple dependencies need to be executed, consider whether each dependency update needs to be executed or whether the dependency changes need to be handled properly. For each dependency update, use Autorun, and if the dependency needs to be processed further, use Reaction.

MobX and React

Tool library

  • mobx
  • react-mobx

Operation thought

  1. The Store is passed from the root component through the Provider provided by the React-Mobx
  2. Use React – Mobx inject for components that need to use store

Specific operation

// index.js
import { store } from './BookStore';
import { preferences } from 'PreferencesStore;
import React, { Fragment } from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'mobx-react';
ReactDOM.render(
  <Provider store={store} userPreferences={preferences}>
    <App />
  </Provider>,
  document.getElementById('root')
);
Copy the code
// component.js
@inject('userPreferences')
@observer
class PreferencesViewer extends React.Component {
  render() {
  const { userPreferences } = this.props;
    / *... * /}}Copy the code

MobX Demo quick start

Here we use Todolist’s example to show how to quickly get started with MobX, and specifically introduce some design ideas of MobX code. The complete code implementation can refer to this Demo.

1. Determine state(Observable and computed)

Let’s take a look at what states todoList needs from the UI design:

  • observable
    • InputText: Input box input
    • The filter: the filter (all/completed/unCompleted)
    • TodoList: List data for Todo
    • Cid: unique identifier of todo
  • computed
    • ShowTodoList: todoList displayed on the page

2. Identify the action for the interaction

  • AddTodo: Adds todo
  • DeleteTodo: Deletes todo
  • ChangeInput: Changes the value of the add input box
  • ChangeTodoStatus: Changes the completed status of todo
  • ChangeTodoText: Changes todo content
  • ChangeFilter: Changes the filter criteria

3. Identify side reactions

  • Logger: Logs are generated when todoList changes
import { configure, observable, computed, action } from 'mobx';
configure({ enforceActions: 'always' });

class TodoListData {
  @observable inputText = ' ';
  @observable todoList = [];
  @observable filter = 'All';
  @observable cid = 0;

  @computed get showTodoList() {
    let showTodoList = this.todoList;
    this.filter === 'Completed' &&
      (showTodoList = this.todoList.filter((todo) = > todo.completed));
    this.filter === 'UnCompleted' &&
      (showTodoList = this.todoList.filter((todo) = >! todo.completed));return showTodoList;
  }

  @action.bound
  addTodo() {
    if (this.inputText) {
      this.todoList.push({
        id: `todo-The ${this.cid++}`.text: this.inputText,
        completed: false});this.inputText = ' ';
    }
  }

  @action.bound
  deleteTodo(id) {
    this.todoList = this.todoList.filter((todo) = >todo.id ! == id); } @action.bound changeInput(value) {this.inputText = value;
  }

  @action.bound
  changeFilter(filter) {
    this.filter = filter;
  }

  @action.bound
  changeTodoStatus(id) {
    this.todoList.find(
      (todo) = >todo.id === id && (todo.completed = ! todo.completed) ); } @action.bound changeTodoText(id, value) {this.todoList.find((todo) = >todo.id === id && (todo.text = value)); }}export default new TodoListData();
Copy the code

conclusion

This article starts from what MobX is, and then compares MobX with Redux to let friends know some advantages of MobX, and then officially enters the world of MobX. To understand the operating mechanism of MobX, the core concepts of MobX and how MobX is used with React. Finally, a small demo was provided for those who just started MobX to understand how to design MobX code. If you have any questions, you are welcome to leave a message in the comment section. Let’s communicate and make progress together! Finally, the cheeky hope to finish reading the article partners point a thumbs-up ~~~

The resources

  • MobX Quick Start Guide
  • MobX basic tutorial