mobx 能为我们带来哪些改变

2020-06-29 11:54:33 浏览数 (1)

介绍

第一印象

首先上一段代码:

代码语言: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。第一小节的例子如果自己写,大致是这个样子的:

代码语言:javascript复制
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占用更多的内存。例如:

代码语言:javascript复制
var student = {name: 'foo'}
var observableStudent = observable(student);

mobx会深拷贝student,然后创建一个{ name: [Getter/Setter] }返回。实际上就“多了”两个student的副本。

2、所有需要监听的变量都必须挂载到一个上级对象上。

单一的变量无法绑定getter和setter,所以,只有ObjectArrayMap才能够被包裹成observable。除此之外,字面量、函数、有prototype的类等都不能监听变化,看下面的代码:

代码语言:javascript复制
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容易使用太多太多。我在另一篇文章中讲吧.....

0 人点赞