1 可观察的数据(observable)
observable是一种让数据的变化可以被观察的方法。
那些数据可被观察?
-原始类型
String、Number、Boolean、Symbol
-对象
-数组
代码中理解数据是如何被观察的:
1)安装mobx的npm依赖
执行命令:npm i mobx -S
2)在index.js文件中,从mobx包中导入observable函数
import { observable } from 'mobx';
3)通过observable将变量转换为可观察的对象
mobx对任意变量对处理方式有两种:
-数组、对象以及es6中的map
直接把observable作为函数来将变量转换为可观察对对象,之后对数组、以及map中的数据进行合理的操作。
>数组(array)
import { observable,isArrayLike } from 'mobx'; // import { observable } from 'mobx'; const arr = observable(['a','b','c']); console.log(arr[0],arr.push('d')); //如果直接用observable函数返回的结果不是数组,而引入isArrayLike模块后,返回的就相当于数组, //可以用一些数组的方法,但注意不能通过数组越界的方式访问或改变数组数据,mobx不会监视越界的访问
>对象(object)
import { observable } from 'mobx'; const obj = observable({a:1,b:1}); console.log(obj.a,obj.b); //mobx只能对已有的数据进行监视,如果要监视新增加的属性,需要使用mobx提供的extendObservable()方法。 //因此最好在程序初始化的时候就说明所有程序会用到的属性。
>map
import { observable } from 'mobx'; const map = observable(new Map()); map.set('a',1); console.log(map.has('a')); map.delete('a'); console.log(map.has('a')); //和数组类型的类似,返回的结果不是真正的map,但表现的足够接近map,可以调用一些map的方法
-其他类型
一些原始类型数据,如String、Number、Boolean等,需要调用observable.box来将变量包装为可观察的对象,之后对该变量的直接赋值将会被监视。
import { observable } from 'mobx'; const num = observable.box(20); const str = observable.box('Hello'); const bool = observable.box(true); // console.log(num,str,bool); //返回的是包装好的可被观察到数据 console.log(num.get(),str.get(),bool.get()); num.set(1); str.set('hi~'); bool.set(false); console.log(num.get(),str.get(),bool.get()); //使用get方法得到原始类型的值,使用set方法修改原始类型的值
4)使用decorator修饰器声明可观察对象
decorator只能修饰类或类的成员,因此我们要创建一个类,在该类中使用observable创建可被观察的数据。
mobx为了简化api对observable函数做了手脚,使其能够识别函数当前是被当作普通函数调用还是被当作修饰器调用的,如果是作为修饰器被调用,就自动识别变量类型,并使用不同的包装转换方案。
import { observable,isArrayLike } from 'mobx'; class Store { @observable array = []; @observable obj = {}; @observable map = new Map(); @observable string = 'Hello'; @observable number = 20; @observable bool = false; }
2 对可观察对数据做出反应(如何知道对象被修改了?)
观察数据变化的方式共四种:computed、autorun、when和Reaction。
computed(可以将多个可观察数据组合成一个新的可观察数据。)
计算值,它将其他可观察数据以我们想要的方式组合起来变成一个新的可观察数据。
有两种使用方法:作为普通函数和作为修饰器。
首先从mobx中引入computed
1)作为普通函数
1⃣️得到数据的计算值:
import { observable,isArrayLike,computed } from 'mobx'; class Store { @observable array = []; @observable obj = {}; @observable map = new Map(); @observable string = 'Hello'; @observable number = 20; @observable bool = false; } var store = new Store(); var foo = computed(function(){ return store.string + '/' + store.number }); // console.log(foo);//ComputedValue对象 //可以使用get方法来获取这个计算值的最终值 console.log(foo.get()); //Hello/20
打印foo的结果:
打印get函数返回的计算结果:
2⃣️监视数据的变化
为了能够监视数据的变化,需要在返回的ComputedValue对象上调用observe方法。
import { observable,isArrayLike,computed } from 'mobx'; class Store { @observable array = []; @observable obj = {}; @observable map = new Map(); @observable string = 'Hello'; @observable number = 20; @observable bool = false; } var store = new Store(); var foo = computed(function(){ return store.string + '/' + store.number }); foo.observe(function(change){ console.log(change); }) store.string = 'world'; store.number = 30;
输出结果:
2)作为修饰器
computed更多是作为decorator修饰器来修饰类的get属性成员的。
import { observable,isArrayLike,computed } from 'mobx'; class Store { @observable array = []; @observable obj = {}; @observable map = new Map(); @observable string = 'Hello'; @observable number = 20; @observable bool = false; @computed get mixed() { return store.string + '/' + store.number; } } var store = new Store();
console.log(store.mixed);//Hello/20
在作为修饰器的情况下,不能再调用observe方法了,通过store.mixed只能获得最终的计算值,这时就要用到另一种方法——autorun。
autorun(可以自动追踪引用的可观察数据,并在数据发生变化时,重新触发)
在可观察数据发生改变之后,自动执行依赖可观察数据的行为,这个行为一般指的是传入autorun的函数。
1)自动运行什么:传入autorun的函数参数
首先从mobx中引入autorun
然后,创建一个autorun,并传入一个没有参数的函数。
同样会打印出计算结果,但不是自动运行的。
import { observable,isArrayLike,computed,autorun } from 'mobx'; class Store { @observable array = []; @observable obj = {}; @observable map = new Map(); @observable string = 'Hello'; @observable number = 20; @observable bool = false; @computed get mixed() { return store.string + '/' + store.number; } } var store = new Store(); autorun(() =>{ console.log(store.string + '/' + store.number);//Hello/20 });
2)怎么触发自动运行?
答:修改autorun中引用的任意可观察数据。
import { observable,isArrayLike,computed,autorun } from 'mobx'; class Store { @observable array = []; @observable obj = {}; @observable map = new Map(); @observable string = 'Hello'; @observable number = 20; @observable bool = false; @computed get mixed() { return store.string + '/' + store.number; } } var store = new Store(); autorun(() =>{ console.log(store.string + '/' + store.number); }); store.string = 'world'; store.number = 30;
3)一些问题
1⃣️当项目中可观察数据较多的时候,数据每发生改变一次就触发一次autorun,会很浪费计算资源。
2⃣️computed值可以作为一种新的可观察数据看待。
autorun(() =>{ console.log(store.mixed); });
上面修改后的代码和2)中结果一致。
computed值除了可以引用普通可观察数据外,还可以嵌套引用其他computed值,但不能有循环引用。
when(提供了执行逻辑的条件,算是一种改进后的autorun)
接收两个函数参数:
- 第一个函数:根据可观察数据返回一个布尔值。当该布尔值为true时,执行第二个函数,且只执行一次。
- 第二个函数:如果可观察数据返回的布尔值一开始就是true,那么立即同步执行第二个函数。
首先从mobx中引入when。
import { observable,isArrayLike,computed,autorun,when } from 'mobx'; class Store { @observable array = []; @observable obj = {}; @observable map = new Map(); @observable string = 'Hello'; @observable number = 20; @observable bool = false; @computed get mixed() { return store.string + '/' + store.number; } } var store = new Store(); when(() => store.bool,() => {console.log("it's true");}); store.bool = true;
无论是否修改了可观察数据,autorun都会先执行一次。
reaction(通过分离可观察数据声明,以副作用的方式,对autorun做出了改进)
单独告知mobx,我们引入了哪些可观察数据。
接收两个函数参数:
- 第一个函数:引用可观察数据,并返回一个值,这个值会作为第二个函数的参数。
- 第二个函数:也叫副作用函数。
首先从mobx中引入reaction。
import { observable,isArrayLike,computed,autorun,when,reaction } from 'mobx'; class Store { @observable array = []; @observable obj = {}; @observable map = new Map(); @observable string = 'Hello'; @observable number = 20; @observable bool = false; @computed get mixed() { return store.string + '/' + store.number; } } var store = new Store(); reaction(() => [store.string,store.number],arr => console.log(arr.join('/'))); store.string = 'world'; store.number = 30;
这样mobx就知道有哪些可观察数据被引用了,并在这些数据被修改后,执行第二个函数。
这样不必执行副作用,就可以建立副作用和可观察数据之间的联系。
在没有数据之前,没必要调用缓存逻辑,可以用reaction实现数据被第一次填充之后再来调用缓存逻辑。
3 修改可观察数据
上一节有一个问题:当项目中可观察数据较多的时候,数据每发生改变一次就触发一次autorun,会很浪费计算资源,在多数情况下,这种高频的触发是没必要的。
为了解决这个问题,mobx提出了action的概念,可以将多次对状态数据的赋值合并为一次,从而减少触发autorun或reaction的次数。
首先从mobx中引入action。
action也是既可以作为普通函数也可以作为decorator来使用,常用的是decorator的方式。
import { observable,isArrayLike,computed,autorun,when,reaction,action } from 'mobx'; class Store { @observable array = []; @observable obj = {}; @observable map = new Map(); @observable string = 'Hello'; @observable number = 20; @observable bool = false; @computed get mixed() { return store.string + '/' + store.number; } @action bar(){ this.string = 'world'; this.number = 30; } } var store = new Store(); reaction(() => [store.string,store.number],arr => console.log(arr.join('/'))); store.bar();
这样就只执行了一次reaction。
作为decorator,action还有一种变种——action.bound,相比与action多了把被修饰的方法的上下文强制绑定到该对象上。
import { observable,isArrayLike,computed,autorun,when,reaction,action } from 'mobx'; class Store { @observable array = []; @observable obj = {}; @observable map = new Map(); @observable string = 'Hello'; @observable number = 20; @observable bool = false; @computed get mixed() { return store.string + '/' + store.number; } @action.bound bar(){ this.string = 'world'; this.number = 30; } } var store = new Store(); reaction(() => [store.string,store.number],arr => console.log(arr.join('/'))); var bar = store.bar; bar();
对代码修改后,对结果没有影响。这种方法在将方法作为callback传给其他镜像时特别有用。
它们两个都需要绑定在预先定义都对象方法上。
mobx还提供了一种语法糖,允许随时定义匿名的action方法,并运行它,这个API叫runInAction。
首先从mobx中引入runInAction,并创建runInAction,直接将对可观察数据修改的代码放到里面,结果和调用store.bar效果是一样的。
import { runInAction,observable,isArrayLike,computed,autorun,when,reaction,action } from 'mobx'; class Store { @observable array = []; @observable obj = {}; @observable map = new Map(); @observable string = 'Hello'; @observable number = 20; @observable bool = false; @computed get mixed() { return store.string + '/' + store.number; } @action.bound bar(){ this.string = 'world'; this.number = 30; } } var store = new Store(); reaction(() => [store.string,store.number],arr => console.log(arr.join('/'))); runInAction(() => { store.string = 'world'; store.number = 30; })
对于有多处重复调用的状态修改逻辑,建议用action来实现复用,否则用runInAction即可;
runInAction也可以接收字符串类型的参数:
runInAction('modify',() => { store.string = 'world'; store.number = 30; })
使用action便于使用debug调试代码。