前言
最近在学习MobX,感觉看官网的文档有些细节理解不会很清楚,所以刷完了官网推荐的《MobX Quick Start Guide》一书,从头梳理了思路写下了这篇文章,希望可以给想要快速了解MobX的小伙伴们提供一些些帮助,如果文章中有描述不清的点或是错误,欢迎大家在评论区跟我交流讨论。
这篇文章从MobX是什么展开,再通过MobX与Redux的对比让小伙伴们了解MobX的一些优势是什么,然后正式进入MobX的世界,去了解以下内容:
- MobX的运行机制是什么样的
- MobX有哪些核心概念
- MobX如何与React结合使用
最后会用一个简单的demo来向大家介绍在项目中使用MobX的思路。接下来,我们就赶快开始吧~
MobX是什么
相信大家在项目开发时一定会用到一些状态管理库,来让状态管理变得更加简单可控,比如说React项目里大家常用的Redux,Vue项目里大家常用的Vuex。而我们的主角MobX也是一个状态管理的工具,它最大的特点是简单、可扩展。
说它简单就不得不提到MobX哲学——任何源自应用状态的东西都应该自动的获得。比如说实际项目经常可能有这样的需求:状态C是根据状态A和状态B计算得到的,所以状态A/B发生改变时,状态C也应该重新计算,在MobX里应用它的computed,当状态A/B发生改变时,它会自动帮我们更新状态C,嘿嘿,是不是很方便呢。
看到这里可能有小伙伴会吐槽Vuex的getter也可以实现这样的功能啊,哈哈,我们先不着急,先继续往下看啥后面会逐步揭开MobX的优势所在的。
MobX与Redux的对比
单向数据流
单向数据流:store管理所有的state变化以及在state发生改变时通知UI和其他观察者
Redux和MobX都是采用的单向数据流,但它们的实现机制是不同的,Redux依赖不可变state快照和两个state快照之间的引用比较来检查是否发生改变,相反,MobX在可变状态下蓬勃发展,并使用粒度(Atoms)通知系统来跟踪状态变化。
开发难度低
Redux的API遵循函数编程风格,对初入门只具备面向对象知识的新人来说有一点理解难度,MobX使用语义丰富的响应式编程风格,对面向对象匹配更加简略的API语法,大大降低了学习成本,同时MobX的集成度也比Redux稍高,避免了让开发者引入众多零散的第三方库。
因为Redux的reducer是纯函数,不能添加副作用,所以中间件是Redux里唯一可以执行副作用的地方,因此Redux的使用经常需要添加许多第三方的中间件,比如常见的用redux-thunk来支持异步操作等。而MobX集成度较高,异步操作等开箱即用,不用添加额外的三方库。
开发代码量少
Redux拥有reducer,actions,store等众多概念,每增加一个状态都要同步更新这些位置,样板代码较多。Mobx只要在store中更新即可,代码编写量上大大少于Redux。
渲染性能好
在react中合理编写shouldComponentUpdate可以避免不必要的重渲染,提升页面性能,但是如果数据层次在复杂的话实现这个方法并非易事,而MobX精确描述哪些组件是需要重渲染的,哪些不需要重渲染,通过合理的组织组件层级和数据结构位置,可以轻易的将视图重渲染控制在最小的限制范围之内,从而影响页面性能。
嘿嘿通过上面的对比是不是觉得MobX看上去还挺不错的样子,简单方便上手快,性能还好,那还犹豫什么,接着往下读,让我们一起进入MobX的世界吧~
MobX的运行机制
我们一起来看一下下面这张模型图,这张模型图描述了单向数据流的思想,也就是说状态的改变会对应页面UI的改变,页面UI上用户事件的操作会触发Action的操作再去更新状态。

但是大家有没有发现一个小问题,就是这张模型图上体现不出副作用(比如网络请求,日志等)该如何处理,那么让我们来更新一下模型图。

这次的模型图上状态的改变不仅要通知UI还要通知副作用的处理器,副作用的处理器收到通知后会执行副作用,然后将副作用产生的改变告诉到actions去更新状态。
哈哈,是不是觉得UI和副作用处理器干的事情很像呢,它们都会接收状态的通知,最后通过Actions来变更状态,所以我们可以认为它们都是观察者,它们在观察状态,一旦状态发生了改变,它们会做出一些相应的响应。

嘿嘿,其实MobX也是这种思想,它的状态叫observables——是可以被观察到变化的对象,它的UI和副作用就是observers——是观察者,它的actions还叫actions哈~~当observables发生改变时会发送消息通知到它的observers,然后observers可以通过actions做一些些处理再通知observables的更新。
上面几张图跟大家分享了MobX是怎么运转的,接下来我们了解一些MobX的核心概念,把MobX用起来!
MobX的核心概念
嘿嘿,因为这部分都是概念性的内容,我们就用极简的方式快速过一遍核心概念和常用的一些API等,后面demo里面讲解怎么用起来,想要了解更多API细节的小伙伴可以参考一下文档撒~
observables
- 概念: 创建一个可观察对象,它可以跟踪发生在它的任何属性上的变化。
- API:
observables
:只能转换对象,数组,map这些observable.box
:可以转换 JavaScript primitives (number, string, boolean, null, undefined), functions, or for class- instances (objects with prototypes)extendObservable(target, object, decorators)
:它允许在运行时混合其他属性,并使它们成为可观察的。observable. map也可以动态的扩展 observable properties,但是它不能动态扩展actions和 computed-properties。
- 装饰器语法
shallow
:只观察第一层数据ref
:不需要观察属性变化(属性是只读的),频繁变更引用时使用refstruct
:对象每次更新都会触发reaction,但是有时只是reference更新了实际属性内容没变,这就是struct存在的意义。struct会基于property做深比较。
actions
- 概念: 接受一个函数,该函数用于修改observables的状态,将在调用操作时被调用。
- 异步actions
runInAction
: runInAction(fn) === action(fn)() 因为对observable的修改必须在action函数内,而async函数是Generator函数的语法糖,await后面的操作放到了回调函数里,因为它不直接在action函数里了,所以我们用runInAction套上一层,保证await后面的操作还在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({});
}
}
}
flow
:解决到处套runInAction的问题 如果有很多个await就要频繁的用runInAction,代码比较不优雅,所以这时可以考虑用flow
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();
- 优点:
- 可读性更好:用actions封装操作会更具语义化
- 性能的大大提升:它会将多次的修改作为一次原子事务,这也可以减少过多通知的噪音
- 深入理解actions的优势
action = untracked(transaction(allowStateChanges(true,\<mutating-function>)))
untracked
: 防止在mutating function中跟踪观察对象(也称为创建新的 observable-observer 关系)transaction
: 批处理通知,在相同的观察对象上强制通知,然后在操作结束时分发最小的通知集allowStateChanges
: 这确保了状态更改确实发生在可观察表上,并且它们将获得新值
这样组合actions的好处是:
- 减少过度的通知
- 通过批量处理最少的一组通知来提高效率
- 尽量减少副作用的执行,对于在一个动作中多次改变的可观察对象
- action通过包装细节,让代码语义化更好,可读性更强
Derivations
任何源自状态并且不会再有任何进一步的相互作用的东西就是衍生。衍生主要有两种,一是computed,二是observers,接下来我们来分别介绍一下它们~
computed
- 概念:可以根据现有的状态或其它计算值衍生出的值。
- 优点:它会缓存上一次的计算值,如果依赖项发生更改它才会重新计算,如果计算结果与之前缓存结果一致也不会触发通知。而且没人用它他会被垃圾回收机制回收掉。
- 装饰器语法
- struct:diff时不是比较reference而是深入比较内容
observers(reactions)
- 概念:观察员,也叫做reactions,包括副作用处理函数和UI。reactions是对状态变化作出响应的动作。
- 类型
autorun
: 依赖项发生变化时自动执行,使用其返回值函数可以将其注销掉reaction
: 精确控制一些依赖的变化满足条件时才做出响应reaction(tracker-function, effect-function): disposer-function
- 参数:
tracker-function
:() => data,追踪所有observables,只要有observable发生变化它就会被执行。它应该返回一个值,用于将它与上一次运行的tracker-function进行比较。如果两次的值不同,effect-function就会被触发。effect-function
:(data) => {},在effect里使用的任何observable是不会被追踪的disposer-function
:是reaction的返回值函数,调用它可以销毁reaction
- 参数:
when
: 只有在满足条件时执行effect-function,并在满足条件后自动处理副作用,是一次性的副作用when(predicate-function, effect-function): disposer-function
- 参数:
predicate-function
: () => booleaneffect-function
: ()=>{}
- 参数:
- 选择恰当observers的方法
判断副作用是否需要执行多次,如果不是只需要执行一次即可则使用when;如果需要执行多次,考虑一下是不是每次依赖更新都要执行,还是需要对依赖的变化进行一些处理满足条件再做,如果是每次依赖更新都做就使用autorun,需要对依赖做进一步处理再决定做不做后续的处理则用reaction。

MobX与React的结合使用
工具库
- mobx
- react-mobx
操作思想
- 通过react-mobx提供的Provider将store从根组件传下去
- 在需要使用store的组件用react-mobx的inject注入

具体操作
// 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')
);
// component.js
@inject('userPreferences')
@observer
class PreferencesViewer extends React.Component {
render() {
const { userPreferences } = this.props;
/* ... */
}
}
MobX demo的快速上手
这里我们用todolist的例子来展示如何快速上手MobX,具体介绍mobx部分代码的设计思路,完整的代码实现可以参考这个Demo。

1. 确定state(observable和computed)
让我们根据UI设计图来看一下todoList需要哪些state:
- observable
- inputText: input框的输入内容
- filter:筛选条件(all/completed/unCompleted)
- todoList:todo的列表数据
- cid:todo的唯一标识符
- computed
- showTodoList: 展示在页面的todoList
2. 确定交互的action
- addTodo:增加todo
- deleteTodo:删除todo
- changeInput:改变添加输入框的值
- changeTodoStatus:改变todo的完成状态
- changeTodoText:改变todo的内容
- changeFilter:改变筛选条件
3. 确定副作用reaction
- logger: todoList变化时打印日志
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-${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();
总结
Emmmmmm,终于写完了,最后我们来个小回顾吧~本文从MobX是什么展开,再通过MobX与Redux的对比让小伙伴们了解MobX的一些优势是什么,然后正式进入MobX的世界,去了解MobX的运行机制是什么样的、MobX有哪些核心概念、MobX如何与React结合使用,最后还给出了一个小demo让刚上手MobX的小伙伴了解如何设计MobX的代码。就希望对初学MobX的小伙伴有用~如果有什么问题也欢迎大家在评论区留言,我们一起交流,共同进步!最后,厚脸皮的希望看完文章的小伙伴点个赞~~~
参考资料
- 《MobX Quick Start Guide》
- MobX入门基础教程