This article was first published in the column of Dawn team of Ali Cloud Front End.

In the initial application of MobX, the project did not have an elegant solution to the data flow management scheme of complex multi-person collaborative projects. By learning and applying the best practices for large maintainable projects in MobX official documents, the project abstracted its own understanding into a simple todoMVC application for everyone to exchange and discuss.

Setting up the development environment

Install the Dawn

Node.js version 7.6.0 or later is required.

$ [sudo] npm install dawn -gCopy the code

Initialization engineering

$ dawn init -t frontCopy the code

I chose to use a front template without dependencies to customize my front-end project.

Directory structure analysis

The project directory generated by the Dawn tool is as follows:

. ├ ─ ─. Dawn# Dawn configuration file├ ─ ─ node_modules ├ ─ ─ the SRC │ ├ ─ ─ assets │ └ ─ ─ index. The js ├ ─ ─test # Unit tests├ ─ ─. Eslintrc. Json ├ ─ ─ the eslintrc. Yml ├ ─ ─ the gitignore ├ ─ ─ the npmignore ├ ─ ─ the README. Md ├ ─ ─ package. The json ├ ─ ─ for server yml └ ─ ─ tsconfig.jsonCopy the code

The most important thing we need to pay attention to is the SRC directory, where index.js is the entry file for our project.

Install dependencies

"devDependencies": {
  "react": "^" 15.6.1."react-dom": "^" 15.6.1
},
"dependencies": {
  "mobx": "^ 3.2.2." "."mobx-react": "^ 4.2.2." ".// Here are the todomvC-style modules
  "todomvc-app-css": "^ 2.1.0." "."todomvc-common": "^" 1.0.4
}Copy the code

Once the dependencies are installed, the environment is configured, and the entire environment setup process takes only three steps, right out of the box, without the tedious configuration of development environments such as Webpack and ESLint. Of course, Dawn also fully supports customizing the configuration of these tools.

todoMVC with MobX

The new project catalog design is as follows:

. ├─ SRC │ ├─ assets# put the static file│ │ ├ ─ ─ common. Less │ │ ├ ─ ─ the favicon. Ico │ │ └ ─ ─ index. The HTML │ ├ ─ ─ the components# Business Components│ │ ├ ─ ─ todoApp. Js │ │ ├ ─ ─ todoEntry. Js │ │ ├ ─ ─ todoItem. Js │ │ └ ─ ─ todoList. Js │ ├ ─ ─ index, js# import file│ ├ ─ ─ models# Data model definition│ ├─ ├─ ├─ exercises# Data Store definition│ │ ├ ─ ─ TodoStore. Js │ │ ├ ─ ─ ViewStore. Js │ │ └ ─ ─ index. The js │ └ ─ ─ utils# utility function│ └ ─ ─ index. Js...Copy the code

The core concepts of MobX data flow practice are the data Model and the data Store.

Defining the data model

The data Model is the Model in MVVM(Model/View/ViewModel). In the early front-end development, the requirements were relatively simple, and most of them were based on the data transmitted by the back-end to directly fill the “pit” in the page, without the consciousness of defining the data model. But as the front-end business complexity and increasing the amount of data transmission, if there is no data model, the definition of the collaboration will make a sharp rise in the front-end of the complexity of the system maintenance and not controllable, manifested intuitively is when other people make changes to data, it is difficult to cover a field can produce the full impact of changes, as a direct result of maintenance cycle and the difficulty of increasing.

Defining a data model has the following benefits:

  • Make the data source controllable, and you can clearly understand the meaning and type of defined fields. It is a natural document of data, which is of great benefit to multi-person collaboration. By applying object-oriented thinking, you can also define properties and methods in the model that can be used by the created instances.
  • To realize front-end data persistence, single-page applications often encounter the problems of multi-page data sharing and real-time update. By defining data models and creating instances, data pulled asynchronously can be avoided from being destroyed after View layer rendering.

Here is the data model definition of the backlog:

import { observable } from 'mobx';
class TodoModel {
  store;
  id;
  @observable title;
  @observable completed;
  /** * Create a TodoModel instance * for a single todo list item * @param {object} store pass TodoStore, Get domain model status and methods * @param {string} ID Instance ID used for front-end operations * @param {string} title content of the todo item * @param {Boolean} Completed Status * @memberof TodoModel */
  constructor(store, id, title, completed) {
    this.store = store;
    this.id = id;
    this.title = title;
    this.completed = completed;
  }
  // Toggle the completion status of list items
  toggle = (a)= > {
    this.completed = !this.completed;
  }
  // Delete list items by ID
  delete = (a)= > {
    this.store.todos = this.store.todos
      .filter(todo= >todo.id ! = =this.id);
  }
  // Set instance title
  setTitle = (title) = > {
    this.title = title; }}export default TodoModel;Copy the code

From the TodoModel definition, it is clear that a backlog has properties and methods that can be used to act on the created instance. However, you can only modify the properties of the instance itself. How can you render the state changes of the to-do items into the View layer through the viewModel?

Define data store

The official document defines data storage as follows:

Stores can be found in any Flux architecture and can be compared a bit with controllers in the MVC pattern. The main responsibility of stores is to move logic and state out of your components into a standalone testable unit.

Stores of data can be found in any Flux architecture and can be compared to controllers in the MVC pattern. Its primary responsibility is to move logic and state from components into a separate, testable unit.

That is, the Store is the bridge between our View layer and our Model layer, the ViewModel, where all state and logic changes should be done. Instead of having multiple instances of the same Store in memory, make sure there is only one instance per Store and allow us to reference it safely.

The following is a project example to understand this process more clearly.

The first is todoMVC’s data Store definition:

import { observable } from 'mobx';
import { uuid } from '.. /utils';
import TodoModel from '.. /models/TodoModel';
class TodoStore {
  // Save the todo list item
  @observable todos = [];
  // Add todo with todo content
  // Note that this is passed in as a reference to the todoStore instance
  // TodoModel has the ability to call todoStore by reference
  addTodo(title) {
    this.todos.push(
      new TodoModel(this, uuid(), title, false)); }}export default TodoStore;Copy the code

Note that this is the reference to todoStore instance passed in when TodoModel is created. This reference gives TodoModel instance the ability to call todoStore. This is why we want to ensure that there is only one instance of the Store where the data is stored.

Then there is the way the view layer renders the data:

import React, { Component } from 'react';
import { computed } from 'mobx';
import { inject, observer } from 'mobx-react';
import TodoItem from './todoItem';
@inject('todoStore')
@observer
class TodoList extends Component {
  @computed get todoStore() {
    return this.props.todoStore;
  }
  render() {
    const { todos } = this.todoStore;
    return (
      <section className="main">
        <ul className="todo-list">
          {todos.map(todo => <TodoItem key={todo.id} todo={todo} />)}
        </ul>
      </section>
    );
  }
}
export default TodoList;Copy the code

Let’s understand this process in stages:

  • First, get the title and completion status of the to-do item, and create a new instance of the to-do item through TodoModel.
  • Second, each TodoModel instance created in todoStore is filled into a ToDOS array for the todo list rendering.
  • Finally, todoStore is injected in the view layer through the Inject decorator, which references the ToDOS array, and MobX responds to the array’s changes by rendering.

If the content and completion status of the to-do item needs to be changed, the corresponding type attributes in the Model need to be modified and then processed in the todoStore to produce a new view display. All we need to do is define the properties that are subject to change as observable variables, modify them as needed, and MobX does the rest for us.

Define user interface state

The todoStore definition is for data storage, but a big part of the front-end is UI state management. UI state usually doesn’t have much logic, but contains a lot of loosely coupled state information, which can also be managed by defining a UI Store.

Here is a simple definition of a UI Store:

import { observable } from 'mobx';
export default class ViewStore {
  @observable todoBeingEdited = null;
}Copy the code

The Store contains only one observable property that holds the TodoModal instance being edited and controls changes to the view-layer to-do list:

. class TodoItem extends Component { ... edit =(a)= > {
    // Set todoBeingEdited to an instance of the current todo
    this.viewStore.todoBeingEdited = this.todo;
    this.editText = this.todo.title; }; . handleSubmit =(a)= > {
    const val = this.editText.trim();
    if (val) {
      this.todo.setTitle(val);
      this.editText = val;
    } else {
      this.todo.delete();
    }
    // Initialize the todoBeingEdited variable after committing the modification
    this.viewStore.todoBeingEdited = null;
  }

  render() {
    // According to the result of comparison between todoBeingEdited and the current todo, determine whether it is edited
    const isEdit = expr((a)= > 
      this.viewStore.todoBeingEdited === this.todo);
    const cls = [
      this.todo.completed ? 'completed' : ' ',
      isEdit ? 'editing' : ' '
    ].join(' ');
    return (
      <li className={cls}>.</li>); }}export default TodoItem;Copy the code

Changes are made to the observable properties of the UI Store in the view, and MobX collects and processes the changes and responds to them in the view.

The source code

The complete todoMVC code can be obtained by:

$ dawn init -t react-mobxCopy the code

Or check out the source code on Github: github.com/xdlrt/dn-te…

conclusion

MobX based data flow management scheme, divided into the following steps:

  • Define data models to make data sources manageable and persistent
  • Define data Store and UI Store, create and manage data Model instances and changes to instance properties
  • Store is injected into the view layer, the data is used to render the view, and MobX automatically updates the view in response to changes in the data

The above is my understanding of using MobX to manage data flow in MVVM framework. Meanwhile, this scheme has been put into practice in a relatively complex project within the team. Currently, the robustness and maintainability of the project is relatively healthy.

You’re gonna have to eat me one last time

Dawn is the open source front-end construction and engineering tool of the front end team of “Ali Cloud – Business Operation Division”.

By encapsulating middleware, such as Webpack and local server, and using it on demand in project pipeline, it can abstract the development process into relatively fixed stages and limited operations, simplify and unify the development environment, and greatly improve the development efficiency of the team.

The project template, the engineering Boilerplate, can also be customized for reuse according to the needs of the team, implementing “Configure Once Run everywhere”. Welcome to experience and give your comments and suggestions to help us improve. Github address: github.com/alibaba/daw…