本文首发于阿里云前端dawn团队专栏。css
项目在最初应用 MobX 时,对较为复杂的多人协做项目的数据流管理方案没有一个优雅的解决方案,经过对MobX官方文档中针对大型可维护项目最佳实践的学习和应用,把本身的理解抽象出一个简单的todoMVC应用,供你们交流和讨论。html
要求 Node.js v7.6.0 及以上版本。前端
$ [sudo] npm install dawn -g复制代码
$ dawn init -t front复制代码
这里我选择使用无依赖的 front 模板,便于自定义个人前端工程。node
由 dawn 工具生成的项目目录以下:react
.
├── .dawn # dawn 配置文件
├── node_modules
├── src
│ ├── assets
│ └── index.js
├── test # 单元测试
├── .eslintrc.json
├── .eslintrc.yml
├── .gitignore
├── .npmignore
├── README.md
├── package.json
├── server.yml
└── tsconfig.json复制代码
其中咱们重点须要关注的是 src 目录,其中的 index.js 就是咱们项目的入口文件。webpack
"devDependencies": {
"react": "^15.6.1",
"react-dom": "^15.6.1"
},
"dependencies": {
"mobx": "^3.2.2",
"mobx-react": "^4.2.2",
// 如下是todoMVC样式模块
"todomvc-app-css": "^2.1.0",
"todomvc-common": "^1.0.4"
}复制代码
安装好依赖,环境就配置完成了,整个环境搭建过程只须要3步,开箱即用,不须要关注 Webpack 和 ESLint 等开发环境的繁琐配置。固然,Dawn 也彻底支持自定义这些工具的配置。git
新的项目目录设计以下:github
...
├── src
│ ├── assets # 放置静态文件
│ │ ├── common.less
│ │ ├── favicon.ico
│ │ └── index.html
│ ├── components # 业务组件
│ │ ├── todoApp.js
│ │ ├── todoEntry.js
│ │ ├── todoItem.js
│ │ └── todoList.js
│ ├── index.js # 入口文件
│ ├── models # 数据模型定义
│ │ └── TodoModel.js
│ ├── stores # 数据store定义
│ │ ├── TodoStore.js
│ │ ├── ViewStore.js
│ │ └── index.js
│ └── utils # 工具函数
│ └── index.js
...复制代码
其中 MobX 数据流实践的核心概念就是数据模型(Model)和数据储存(Store)。web
数据模型即为 MVVM(Model/View/ViewModel) 中的 Model。早期的前端开发,需求比较简单,大可能是基于后端传输的数据去直接填充页面中的“坑位”,没有定义数据模型的意识。但随着前端业务复杂度和数据传输量的不断上升,若是没有数据模型的定义,在多人协做时会让前端系统维护的复杂性和不可控性急剧上升,直观体现就是其它人对数据作改动时,很难覆盖到改动的某个字段会产生的所有影响,直接致使维护的周期和难度不断增长。npm
定义数据模型有如下好处:
下面是待办事项的数据模型定义:
import { observable } from 'mobx';
class TodoModel {
store;
id;
@observable title;
@observable completed;
/** * 建立一个TodoModel实例 * 用于单个todo列表项的操做 * @param {object} store 传入TodoStore,获取领域模型状态和方法 * @param {string} id 用于前端操做的实例id * @param {string} title todo项的内容 * @param {boolean} completed 是否完成的状态 * @memberof TodoModel */
constructor(store, id, title, completed) {
this.store = store;
this.id = id;
this.title = title;
this.completed = completed;
}
// 切换列表项的完成状态
toggle = () => {
this.completed = !this.completed;
}
// 根据id删除列表项
delete = () => {
this.store.todos = this.store.todos
.filter(todo => todo.id !== this.id);
}
// 设置实例title
setTitle = (title) => {
this.title = title;
}
}
export default TodoModel;复制代码
从 TodoModel 的定义中能够清楚的看到一个待办事项拥有的属性和方法,经过这些,就能够对建立出的实例进行相应的操做。可是在实例中只能修改实例自身的属性,怎样才能把待办事项的状态变化经过 viewModel 来渲染到 view 层呢?
官方文档对数据储存的定义是这样的:
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.
翻译过来是:数据储存(Store)能够在任何 Flux 系架构中找到,能够与 MVC 模式中的控制器(Controller)进行类比。它的主要职责是将逻辑和状态从组件中移至一个独立的,可测试的单元。
也就是说,Store 就是链接咱们的 View 层和 Model 层之间的桥梁,即 ViewModel,全部的状态和逻辑变化都应该在 Store 中完成。同一个 Store 不该该在内存中有多个实例,要确保每一个 Store 只有一个实例,并容许咱们安全地对其进行引用。
下面经过项目示例来更清晰的理解这个过程。
首先是 todoMVC 的数据 Store 定义:
import { observable } from 'mobx';
import { uuid } from '../utils';
import TodoModel from '../models/TodoModel';
class TodoStore {
// 保存todo列表项
@observable todos = [];
// 添加todo,参数为todo内容
// 注意:此处传入的 this 即为 todoStore 实例的引用
// 经过引用使得 TodoModel 有了调用 todoStore 的能力
addTodo(title) {
this.todos.push(
new TodoModel(this, uuid(), title, false)
);
}
}
export default TodoStore;复制代码
须要注意的是,在建立 TodoModel 传入的 this 即为 todoStore 实例的引用,经过这里的引用使得 TodoModel 的实例拥有了调用 todoStore 的能力,这也就是咱们要保证数据储存的 Store 只有一个实例的缘由。
而后是视图层对数据进行渲染的方式:
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;复制代码
咱们把这个过程分步来理解:
若是待办事项的内容和完成状态须要改动,就要修改 Model 中对应的类型属性,而后在 todoStore 中进行相应的加工,最后产出新的视图展现。而在这个过程当中,咱们只须要把可能会变化的属性定义为可观察的变量,在须要变动的时候进行修改,剩余的工做 MobX 会帮咱们完成。
刚才定义的 todoStore 是针对数据储存的,可是对于前端来说,还有很大一部分工做是 UI 的状态管理。
UI 的状态一般没有太多的逻辑,但会包含大量松散耦合的状态信息,一样能够经过定义 UI Store 来管理这部分状态。
如下是一个 UI Store 的简单定义:
import { observable } from 'mobx';
export default class ViewStore {
@observable todoBeingEdited = null;
}复制代码
这个 Store 只包含一个可观察的属性,用于保存正在编辑的 TodoModal 实例,经过这个属性来控制视图层待办事项的修改:
...
class TodoItem extends Component {
...
edit = () => {
// 设置 todoBeingEdited 为当前待办事项todo的实例
this.viewStore.todoBeingEdited = this.todo;
this.editText = this.todo.title;
};
...
handleSubmit = () => {
const val = this.editText.trim();
if (val) {
this.todo.setTitle(val);
this.editText = val;
} else {
this.todo.delete();
}
// 提交修改后初始化 todoBeingEdited 变量
this.viewStore.todoBeingEdited = null;
}
render() {
// 根据 todoBeingEdited 和当前 todo 比较的结果判断是否处于编辑状态
const isEdit = expr(() =>
this.viewStore.todoBeingEdited === this.todo);
const cls = [
this.todo.completed ? 'completed' : '',
isEdit ? 'editing' : ''
].join(' ');
return (
<li className={cls}> ... </li>
);
}
}
export default TodoItem;复制代码
在视图中对 UI Store 的可观察的属性进行修改,MobX 会收集相应的变化通过处理后响应在视图上。
完整的 todoMVC 代码能够经过如下方式获取:
$ dawn init -t react-mobx复制代码
或者在 Github 上查看源码:github.com/xdlrt/dn-te…
基于 MobX 的数据流管理方案,分为如下几步:
以上是我对 MVVM 框架中使用 MobX 管理数据流的一些理解,同时这种方案也在团队内一个较为复杂的项目中进行实践,目前项目的健壮性和可维护性比较健康,欢迎提出不一样的看法,共同交流。
Dawn 是「阿里云-业务运营事业部」前端团队开源的前端构建和工程化工具。
它经过封装中间件(middleware) ,如 webpack 和本地 server ,并在项目 pipeline 中按需使用,能够将开发过程抽象为相对固定的阶段和有限的操做,简化并统一开发环境,可以极大地提升团队的开发效率。
项目的模板即工程 boilerplate 也能够根据团队的须要进行定制复用,实现「configure once run everywhere」。
欢迎体验并提出意见和建议,帮助咱们改进。Github地址:github.com/alibaba/daw…