Mobx是一个状态管理的库, 也许你对应用中一些数据的管理已经得心应手了,并且有自己擅长或喜好的状态管理库,但这也并不会影响你对Mobx展开了解,因为这些库或者工具提供给我们的是解决问题的方案,存在即有其合理性,既然Mobx存在,并受到不少人的推崇,那肯定有其优势所在,基于这个原因,我们也是可以花那么一丢丢时间去熟悉下的,也许会对我们有莫大的帮助。我也是基于这么个想法去了解熟悉了下mobx,并做个总结。
Mobx是什么
mobx是一个状态管理的库,它不依赖于视图层的框架, 不像vuex,是vue的定制版。它与任何的视图层框架都是可以配合使用的, 但是和它最配的就是React了。可是React不是已经有了黄金搭档Redux么, 为啥还出来一个第三者呢,并且还受到不少人的喜爱, 连Redux的作者都推荐过mobx,还有不少人将应用从Redux重构为Mobx。这说明Mobx确实有其吸引人的地方,这里不谈两者的对比,这一系列文章还是比较多的,不过可以先从这里入手一波Mobx。
状态管理解决什么问题
- 组件与组件之间的数据共享问题
- 将应用的状态统一管理, 组件只负责数据渲染,增强系统的一个可维护(这个是自己的一个观点,有待讨论)
Mobx的原理
非常的清晰, 从状态state开始,这里的state是可观察的,state变更, 若computed和reactions有依赖变更的数据则会自动执行。action动作触发state的更改,,而且Mobx的核心也就这几个概念, observable state(可观察状态)、reactions(衍生: 状态变更后产生的一些副作用)、action(动作:更改state),接下来入手2个demo就能明白这些概念了。Demo1:
import { observable, autorun } from 'mobx'
const obsObj = observable({
title: 'hello world'
})
autorun(() => {
console.log('mobx observal title',obsObj.title)
})
obsObj.title = 'title change'
复制代码
输出的结果是:
otuput:
mobx observal title: hello world
mobx observal title: title change
复制代码
将一个对象通过observable方法变为可观察的, 执行aurorun中的函数, 状态变更后autorun中的函数自动的再次执行。可以看出Mobx自身实现了一套响应式原理,没读过源码, 但大致可以知道其实现使用了观察者模式,第一遍执行autorun时有调用对象属性的对方对其get方法进行拦截, 并将这个函数加入到其依赖中, 在执行set的时候,执行收集的依赖。其中autorun就是原来图中的reaction衍生, state就是observable之后的可观察状态对象。
Demo2:
import {observable, autorun, action} from 'mobx'
const obsObj = observable({
title: 'hello world',
get extendTitle() {
return `${this.title} mobx`
}
})
autorun(() => {
console.log('mobx computed title:', obsObj.extendTitle)
})
const fn = action(function(){
obsObj.title = 'title change'
})
fn()
复制代码
输出结果是:
otuput:
mobx computed title:' hello world mobx
mobx observal title: title change mobx
复制代码
相比上个例子,多了一个computed属性, 即extendTitle, 它也是会变为可观测的, 可以知道extendTitle依赖了title,所以title变了后, computed自动生成新的值,而autorun依赖的extendTitle变了,所以再次自动执行。(值得注意的是若没有计算属性是只有运用到时才会去执行重新计算逻辑)。还有一个action, 它是将要变更数据的方法用action包裹一下, 这个不是必须的, 但是在大型复杂应用中这是很有必要的,因为状态的变更一定是发生在action里, 更容易定位问题。 因此mobx有配置参数可以设置更改observable的数据必须要用action包裹。
至此mobx的执行流程以及重要的概念已经通过上述的两个demo比较清晰的理解了。
Mobx API
mobx的api也是比较简单并且好理解的,可以将其分为 三类, 上面的demo中使用的api都有各自的归属, 这部分简单讲下这三类api,当然是自我理解后的一些精华部分, 具体并完整的那莫过于官网了, 并且也不是一篇文章能讲完的。
1. observable类api
mobx可以将JS基本数据类型、引用类型、普通对象、类实例、数组、映射、Set变为一个可观察的数据。
* observable
这是一个便捷的api,observable(value), 只有value是object(普通对象), Array, Map, Set能转换成功。原始数据类型、非普通对象、函数会报错。 注: 普通对象指的是一个对象的原型是Object,或者没有原型,即用花括号创建的对象字面量或者Object.create(null)创建的对象
* observable.box
可以是任何数据,一般用于基本数据类型,设置和获取以及变为可观测的使用方式和observable有所不同。例如官网的一个demo:
import {observable} from "mobx";
const cityName = observable.box("Vienna");
console.log(cityName.get());
cityName.observe(function(change) {
console.log(change.oldValue, "->", change.newValue);
});
cityName.set("Amsterdam");
复制代码
* extendObservable
extendObservable用于将用构造函数生成的对象以及Object.create原型非null的对象变为可观察。
var Person = function(firstName, lastName) {
// 在一个新实例上初始化 observable 属性
extendObservable(this, {
firstName: firstName,
lastName: lastName,
get fullName() {
return this.firstName + " " + this.lastName
},
setFirstName(firstName) {
this.firstName = firstName
}
}, {
setFirstName: action
});
}
var matthew = new Person("Matthew", "Henry");
extendObservable(matthew, {
age: 353
});
复制代码
以上几个api基本上完成了绝大多数我们应用的诉求,将数据变为可观察。
2. reactions类api
reactions指的是对可观察的数据做出相应,可以是产生一个新的值, 也可以是产生一些副作用, 比如打印日志,更新视图等的逻辑。
* computed
非常好理解的一个api, 就是计算属性, 会根据依赖的可变化数据生成一个新的值, 并且这个新的值也是可观察。mobx对性能上是做到了很好的优化的,这个计算属性的值,在某轮状态变化的过程中没有被用到,那么它是不会被重新计算执行的,并且如果这个值不再被引用了, 那么也会被垃圾回收掉。
* autorun
这个就是响应式函数, 依赖的状态变化后, 自动执行该函数, so easy。
* when 和 reaction
这两个完全可以看做是autorun的语法糖,是autorun功能的一个增强。官网看下api非常好理解的。
注意点: 我们要弄清楚衍生它会对什么做出响应,MobX 会对在追踪函数执行过程中读取现存的可观察属性做出反应。就是在衍生里执行了读取可观察属性的操作,则它会响应后续的状态变更。
3. action 类的api
action就是动作, 触发可观察状态的变更, 形成一个闭环,从上面的两个demo其实可以看出,action包裹并不是必须的,但是大型应用中将一些数据变更的操作都强制用action包裹是很有必要的, 这样对系统的维护性以及排查问题还是很有用的。
* action
如果需要将动作强制action包裹, 需要全局配置mobx的config:
const { configure} from "mobx"
configure({
enforceActions: 'always'
});
复制代码
* runInAction 工具函数
如果action包裹的函数中存在异步的回调函数(promise的then,setTimeout里的回调), 如果里面改变了状态则也需要用将其用action包裹,runInAction它也是action的语法糖, 将异步回调中的最终的状态修改的部分放到一个函数中,用runInAction包裹。
* flows
将异步用generator的方式写,然后用flows包裹。
装饰器API
上面的那些三类api就是mobx的思想所在, 但是mobx正真方便使用姿势是装饰器模式。如下使用装饰器的两个demo所示, 包含以上三类api的使用:
demo1:
import { observable, computed, autorun } from "mobx";
class OrderLine {
@observable price = 0;
@observable amount = 1;
@computed get total() {
return this.price * this.amount;
}
}
const obj = new OrderLine()
autorun(() => {
console.log(obj.amount)
})
obj.amount = 2
复制代码
上述是在babel或者ts的转换下,支持用装饰器写法的一个coding, 如果不支持, 也提供了decator api如下:
demo2:
import { observable, computed, action, decorate } from "mobx"
class OrderLine {
price = 0;
amount = 1;
get total() {
return this.price * this.amount;
}
setPrice() {
this.price = 2
}
}
decorate(OrderLine, {
price: observable,
amount: observable,
total: computed,
setPrice: action,
})
复制代码
mobx的调试
mobx内置了trace API,该API可以帮助我们在开发的时候进行调试。可以通过在衍生中(computed,autorun等响应函数)加入trace(true),能够在响应状态变化时进行debugger,通过调试信息我们能够看出是哪个状态的变化触发了它的响应、这个衍生依赖哪些状态,以及定位此次变化的代码。
Demo:
import {observable, autorun, trace} from 'mobx'
const obsObj = observable({
title: 'hello world',
name: 'trace demo'
})
autorun((r) => {
trace(true)
console.log('name: ', obsObj.name)
})
obsObj.name = 'title changed'
复制代码
上面demo中,可观察状态obsObj.name发生变化时,自动执行autorun, 然后在chrome可以看到如下的断点信息:
其中标明了trace Autorun@3的一些信息,Autorun@3就是mobx给这个衍生的一个命名, Autorun@3更新了,是因为[email protected] 让其更新了, 同时下面也列出了这个衍生的的所有依赖。在这次函数堆栈中, 至少看6-8的堆栈帧就能找到改变这个状态的代码。mobx与mvvm框架的结合
通过上述mobx的了解,大概知道其如何运行以及一些重要的api,它是不依赖于视图层框架的, 即它可以和我们主流的React、VUE、Angular结合的,只需要一个连接器,但它还算和React搭配运用的最多了。友mobx原理不难理解, 只需要将视图的render作为一个衍生就行了,render里的状态都变为可观察的,状态变了就重新render,更新视图。即如下图所示:
我么可以通过官方的一个todo demo来了解mobx+react的应用开发: 传送门总结
以上是mobx一些主要的思想以及api, 通过这边文章可以用更少的时间去了解上手mobx, 如果需要深入可以仔细去研读mobx的官网了,以及mobx和redux的对比文章也是可以搜索出不少的。