一、Mobx工作流程图
在整个数据流中,通过事件驱动(UI 事件、网络请求…)触发 Actions,在 Actions 中修改了 State 中的值,这里的 State 既应用中的 store 树(存储数据),然后根据新的 State 中的数据计算出所需要的计算属性(computed values)值,最后响应(react)到 UI 视图层。
二、Mbox的使用
新建一个mobx目录,在其中新建一个appStore.js文件,专门用于管理整个app的state。appStore中定义一个Component,在组件中:
1:定义需要被全局观察的state,用@observable修饰
@observable state = { toDoList: [], };
2:定义改变state的行为函数,用@action修饰
@action setToDoList = (payload) => { this.state.toDoList.push(payload.toDoItem); }
3:定义基于state变化,自动触发的行为函数,用@autorun修饰
autorun(() => { console.info('toDoList:', this.state.toDoList); });
4:在文件末尾,新建一个该组件的实例,并export
5:mobx-react实现mobx与react的链接(直接在根组件中使用)
import { Provider } from 'mobx-react'; import App from 'widget/App/AppView'; import * as stores from 'config/Stores'; <Provider {...stores}> <App /> </Provider>
6:使用 @inject 给组件注入其需要的 store(利用 React context 机制)
通过 @observer 将 React 组件转化成响应式组件,它用 mobx.autorun 包装了组件的 render 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件
三、Observable Object和Observable Arrays
如果使用observable来修饰一个Javascript的简单对象,那么其中的所有属性都将变为可观察的,如果其中某个属性是对象或者数组,那么这个属性也将被observable进行观察,说白了就是递归调用。
Tips: 简单对象是指不由构造函数创建,而是使用Object作为其原型,或是干脆没有原型的对象。
考虑到ES5中原生数组对象中存在一定的限制,所以Mobx将会创建一个类数组对象来代替原始数组。在实际使用中,这些类数组的表现和真正的原生数组极其类似,并且它支持原生数组的所有API,包括数组索引、长度获取等。
但是注意一点,sort和reverse方法返回的是一个新的Observable Arrays,对原本的类数组不会产生影响,这一点和原生数组不一样。
四、MobX 追踪属性访问,而不是值
let message = observable({ title: "Foo", author: { name: "Michel" }, likes: [ "John", "Sara" ] })
在内存中看起来像下面这样。 绿色框表示可观察属性。 请注意,值 本身是不可观察的!
现在 MobX 基本上所做的是记录你在函数中使用的是哪个箭头。之后,只要这些箭头中的其中一个改变了(它们开始引用别的东西了),它就会重新运行。
常见陷阱: console.log
const message = observable({ title: "hello" }) autorun(() => { console.log(message) }) // 不会触发重新运行 message.title = "Hello world
在上面的示例中,更新 message 的 title 属性不会被打印出来,因为没有在 autorun 内使用。autorun 只依赖于 message,它不是 observable,而是常量。换句话说,对于 MobX 而言,它没有使用 title,因此与 autorun 无关。
import { toJS } from 'mobx'; autorun(() => { console.log(message.title) }) autorun(() => { console.log(mobx.toJS(message)) // toJS 创建了深克隆,从而读取消息 }
可以使用以上两种方式来打印出message 的 title 属性
五、编写异步 Actions (动作)
action 包装/装饰器只会对当前运行的函数作出反应,而不会对当前运行函数所调用的函数(不包含在当前函数之内)作出反应! 这意味着如果 action 中存在 setTimeout、promise 的 then 或 async 语句,并且在回调函数中某些状态改变了,那么这些回调函数也应该包装在 action 中。
runInAction 工具函数
注意,runInAction 还可以给定第一个参数作为名称。runInAction(f) 实际上是 action(f)() 的语法糖。
import { action, runInAction } from 'mobx'; @action getToDoList = async () => { const { data } = await Serv.getToDoList(); const toDoList = get(data, 'list', []); runInAction(() => { this.state.toDoList = toDoList; }); }
六、(@)computed
计算值(computed values)是可以根据现有的状态或其它计算值衍生出的值。 概念上来说,它们与excel表格中的公式十分相似。 不要低估计算值,因为它们有助于使实际可修改的状态尽可能的小。 此外计算值还是高度优化过的,所以尽可能的多使用它们。
不要把 computed 和 autorun 搞混。它们都是响应式调用的表达式,但是,如果你想响应式的产生一个可以被其它 observer 使用的值,请使用 @computed,如果你不想产生一个新值,而想要达到一个效果,请使用 autorun。 举例来说,效果是像打印日志、发起网络请求等这样命令式的副作用。
import { observable, useStrict, computed, } from 'mobx'; useStrict(true); class YaoFangMod { @observable state = { toDoList: [], }; @computed get length() { return this.state.toDoList.length + 1;