介绍
第一印象
首先上一段代码:
代码语言:javascript复制import {observable, observe} from 'mobx'
var student = observable({
name: 'unknown'
})
observe(student, (change)=>{
console.info(change)
/* will print:
* { type: 'update',
* object: { name: [Getter/Setter] },
* oldValue: 'unknown',
* name: 'name',
* newValue: 'foo' }
*/
})
student.name = 'foo'
非常直观的,student的属性改变了,监听的方法打印出了所有的变动细节。
简介
mobx是一个状态管理器,用于TFRP(transparently applying functional reactive programming)自发式函数相应式编程。
可以看到,它的核心概念包括action->state->computed value -> reaction
通过action数据(state), 然后state引发的变动触发computed-value变动,最终传给监听器(reaction),如果把react的的render方法也注册为监听器,那么mobx就可以和react配合起来构建一个健全的app架构了。
基本使用
mobx的原理是在对象包裹setter和getter。例如当我们调用一个属性时候:obj.name
就会触发name的getter,调用obj.name = 'hello world'
的时候,就会触发name属性的setter。第一小节的例子如果自己写,大致是这个样子的:
var student = {
_name: 'unknown',
get name(){
console.info('getter of student.name')
return this._name;
},
set name(val){
console.info('setter of student.name')
this._name = val;
}
}
student.name = 'richcao' //=> setter of student.name
student.name //=> getter of student.name
特性和局限
很难单独的说mobx带来哪些特性和哪些局限,这里都是结合react,以实际工程使用角度来考量mobx的优劣。
特性
属性变动精确定位
这点特性联系到页面重绘:
代码语言:javascript复制var student = observable({
name: 'richcao',
age: 28
})
ReactDom.render(
mobx.observer(()=>(
<div>{student.name}<div>
)),
document.getElementById('root')
);
student.name = 'foo' //会引发重绘
student.age = 18 // 不会引发重绘
如果要用redux实现这点,估计需要不计其数的shouldComponentUpdate
方法了吧。
视图层缓存
mobx提供了一个computed方法,通过computed方法计算的值如果放到了监听器中,那么这个值会被缓存,state没有变动的情况下,computed的值不会重新计算。
想想我们的react中的render方法, 在方法中创建的中间变量都会被重新计算,例如下面的代码,每render一次,就需要计算一次totalPrice,简单的场景还好,如果计算量偏大,这样的消耗就不该发生。
代码语言:javascript复制render(){
let totalPrice = this.props.price * this.props.num;
return <div>{totalPirce}</div>
}
可不可以把totalPrice缓存起来,只要props.price和pros.num没有变化,就不用重新计算totalPrice?答案是可以的。mobx提供了一个computed方法,用于将state映射为新的值,这些值常常被用作视图层的渲染。看下面的例子:
代码语言:javascript复制const a = observable(1);
const b = observable(2);
const sum = computed(() => {
console.log("computing");
return a.get() b.get();
});
sum.get() // outputs "computing", returns result
sum.get() // outputs "computing", returns result
sum.get() // outputs "computing", returns result
// create an observer (reaction/autorun/render)
const disposeObserver = autorun(() => sum.get()) // outputs "computing", result is cached
sum.get() // outputs nothing, cached value returned
sum.get() // outputs nothing, cached value returned
sum.get() // outputs nothing, cached value returned
// dispose observer
disposeObserver()
sum.get() // outputs "computing", returns result
sum.get() // outputs "computing", returns result
sum.get() // outputs "computing", returns result
}
局限
使用mobx需要妥协的有:
1、内存上的增加:
mobx会将给定对象深拷贝一份作为私有变量,然后再创建一个对象,包含该私有变量所有属性的getter和setter方法。无疑在内存占用上,比单纯的使用plain javascript object
占用更多的内存。例如:
var student = {name: 'foo'}
var observableStudent = observable(student);
mobx会深拷贝student,然后创建一个{ name: [Getter/Setter] }
返回。实际上就“多了”两个student的副本。
2、所有需要监听的变量都必须挂载到一个上级对象上。
单一的变量无法绑定getter和setter,所以,只有Object
、Array
、Map
才能够被包裹成observable
。除此之外,字面量、函数、有prototype的类等都不能监听变化,看下面的代码:
var age = observable(23);
mobx.observe(age, console.info)
age = 18; // nothing happened
关于原生变量的情况,mobx返回的是一个observable.box
对象,可以去官方文档中查看。其实这一点我们注意避免即可。
3、全局state维持同一对象引用
state维持为同一个对象的引用,这样要做状态回滚(撤销操作)就比较困难了。
我们知道redux的每次store更新,都生成一个新的state引用,可以把旧的state保存下来,需要撤销操作时,将旧的state重新render一遍就好了。
这个特性放到mobx上就不太可能了,因为我们压根儿没有Object.assign一个新的state出来。好在,mobx团队也给出了解决方案:mobx-state-tree。其底层和immutable一样,是一个不可变类型的数据容器,但是在api上比immutablejs容易使用太多太多。我在另一篇文章中讲吧.....