Mobx
安装
安装:npm install mobx --save
。 React 绑定库: npm install mobx-react --save
observable
observable是一种让数据的变化可以被观察的方法
那么哪些数据可以被观察、怎么获取可观察的数据
- 普通类型: String、Number、Boolean、Symbol
- 复杂类型:Object、Array、map
复杂类型
- Array
import {observable,isArrayLike} form 'mobx'
const arr = observable(['a','b','c']);
console.log(arr,Array.isArray(arr),isArrayLike) // false true
// 可以发现已经不是一个数组了,已经被转换为可观察数据了。
// 那么对于可观察数据怎么判断他是一个数组呢? 用mobx提供的方法 isArrayLike
console.log(arr[0],arr[1]。arr.pop()); // a, b,c
// 虽然转换后不是一个真的数组, 但是它模拟的数组大部分的功能和方法,比如用下标取值,但是不能使用下标越界的方式来访问(比如: 数组长度为3,你下标取3,这在js 中不会报错,但是在这会警告,越界的元素不会被mobx所监视)
- Object、map
import {observable} form 'mobx'
const obj = observable({a:1,b:2}});
const map = observable(new Map());
//会将其转换为可观察的对象
map.set('a',1);
console.log(obj.a, obj.b) // 1, 2
cossole.log(map.has('a')) // true
// mobx不会监视对象上新增加的属性,如果要监视新增加的属性要使用extendObservable(),所以建议在初始化的时候声明所有要使用到的属性
普通类型
对于复杂类型的数据,可以直接使用observable将数据转换为可观察的数据,对于普通类型的数据必须使用observable.box来将数据转换为可观察的数据。
import {observable} form 'mobx'
let num = observable.box(100);
let str = observable.box('str');
let bool = observable.box(true);
console.log(num,str,bool);// 都转换为ObservableValue类型
// 获取普通类型数据的原始值必须使用get()方法。
console.log(num.get(),str.get(),bool.get()) // 100, 'str', true
// 调用set方法会修改原始的值
num.set(50);
str.set('123');
bool.set(fasle);
console.log(num.get(),str.get(),bool.get()) // 50, '123', false
但是在实际的使用中,我们不需要使用observable.box来处理普通数据,我们还是使用observable来声明就可以了,mobx将我们处理了。
实际使用、结合es6 decorator语法
import {observable} form 'mobx'
class Store{
@observable arr = [];
@observable obj = {};
@observable mao = new Map();
@observable num = 1;
@observable str = 'str';
@observable bool = true;
}
对可观察数据做出反应
观察数据变化的方式:
- computed
- autorun
- when
- Reaction
computed
。计算值(computed values)是可以根据现有的状态或其它计算值衍生出的值。简单理解为对可观察数据做出的反应,多个可观察属性进行处理,然后返回一个可观察属性
使用方式:1、作为普通函数,2、作为decorator
import {observable} form 'mobx'
class Store{
@observable arr = [];
@observable obj = {};
@observable mao = new Map();
@observable num = 1;
@observable str = 'str';
@observable bool = true;
// 2. 作为decorator
@computed get mixed(){
return store.str + '/'+ store.num
}
}
const store = new Store();
// 1. 作为普通函数
let foo = computed(function(){
return store.str + '/'+ store.num
})
// computed 接收一个方法,里面可以使用被观察的属性
// 监控数据变化的回调,当foo里面的被观察属性变化的时候 都会调用这个方法
foo.observe(function(change){
console.log(change) // 包含改变值foo改变前后的值
})
store.str = '1';
sotre.num = 2;
autorun
当我们使用decorator来使用computed,我们就无法得到改变前后的值了,这样我们就要使用autorun方法。
从方法名可以看出是“自动运行”。 所以我们要明确两点: 自动运行什么,怎么触发自动运行
自动运行传入autorun的参数 , 修改传入的autorun的参数修改的时候会触发自动运行
import {observable,autorun} form 'mobx'
class Store{
@observable arr = [];
@observable obj = {};
@observable mao = new Map();
@observable num = 1;
@observable str = 'str';
@observable bool = true;
// 2. 作为decorator
@computed get mixed(){
return store.str + '/'+ store.num
}
}
const store = new Store();
autorun(() => {
console.log(store.str + '/'+ store.num)
})
store.str = '1';
sotre.num = 2;
when
用法: when(predicate: () => boolean, effect?: () => void, options?)
when 观察并运行给定的 predicate,直到返回true。 一旦返回 true,给定的 effect 就会被执行,然后 autorunner(自动运行程序) 会被清理。 该函数返回一个清理器以提前取消自动运行程序。
在autorun中我们的被观察属性一改变就会自动运行,但是在我们常见的业务场景中,只用当某一个条件触发的时候,才去执行一些操作。
import {observable,when} form 'mobx'
class Store{
@observable arr = [];
@observable obj = {};
@observable mao = new Map();
@observable num = 1;
@observable str = 'str';
@observable bool = false;
// 2. 作为decorator
@computed get mixed(){
return store.str + '/'+ store.num
}
}
const store = new Store();
when(() => store.bool,()=> {
console.log('it's a true)
})
store.bool = true;
when方法接收两个参数(两个方法),第一个参数根据可观察属性的值做出判断返回一个boolean值,当为true的时候,执行第二个参数。如果一开始就返回一个true,就会立即执行后面的方法。
reaction
用法:reaction(() => data, (data, reaction) => { sideEffect }, options?)
它接收两个函数参数,第一个(数据 函数)是用来追踪并返回数据作为第二个函数(效果 函数)的输入。 不同于 autorun 的是当创建时效果 函数不会直接运行(第二个参数不会立即执行,autorun会立即执行传入的参数方法),只有在数据表达式首次返回一个新值后才会运行。 在执行 效果 函数时访问的任何 observable 都不会被追踪。
import {observable,reaction} form 'mobx'
class Store{
@observable arr = [];
@observable obj = {};
@observable mao = new Map();
@observable num = 1;
@observable str = 'str';
@observable bool = false;
// 2. 作为decorator
@computed get mixed(){
return store.str + '/'+ store.num
}
}
const store = new Store();
reaction(() => [store.str,store.num],(arr) => console.log(arr.join('\')))
store.str = '1';
sotre.num = 2;
当初始化的时候 程序会先执行reaction中的第一个参数方法,确定那些被观察数据被引用了,然后当被引用的数据被修改的时候,就会将执行第二个参数
修改可观察数据(action)
前面我们修改可观察数据都是通过手动修改可观察数据的值这种方式(直接赋值)。但是这种方式有一个很大的副作用,比如都会触发autorun、reaction。 但是如果用户的一次点击操作,会导致很多个可观察数据的改变,这样会导致autorun执行很多次,但是用户只希望执行一次UI渲染就可以了。
action的作用就是多次对状态数据的赋值,合并成一次。从而减少对autorun、reaction的次数。
import {observable,reaction, } form 'mobx'
class Store{
@observable arr = [];
@observable obj = {};
@observable mao = new Map();
@observable num = 1;
@observable str = 'str';
@observable bool = false;
// 2. 作为decorator
@computed get mixed(){
return store.str + '/'+ store.num
}
@aciton
bar(){
store.str = '5';
sotre.num = 4;
}
}
const store = new Store();
reaction(() => [store.str,store.num],(arr) => console.log(arr.join('\')))
store.str = '1';
sotre.num = 2;
// reaction 触发了两次
store.bar() // 通过action来修改可观察数据, 只触发了一次reaction
action.bound 可以用来自动地将动作绑定到目标对象。 注意,与 action 不同的是,(@)action.bound 不需要一个name参数,名称将始终基于动作绑定的属性。类属于绑定this
@aciton.bound bar(){
store.str = '5';
sotre.num = 4;
}
let barCopy = store.bar;
barCopy();
runInAction 是个简单的工具函数,它接收代码块并在(异步的)动作中执行。
Mobx-react
简单的理解就是将组件的render方法包装为autorun
import {observable,action} from 'mobx';
import React,{Component} from 'react';
import ReactDom from 'react-dom';
import PropTypes from 'prop-types';
import { observer, PropTypes as ObservablePropTypes} from 'mobx-react'
class Store {
@observable cache = {
queue:[]
}
}
const store = new Store();
class Bar extends Component{
static propTypes = {
queue: PropTypes.array
};
render(){
const queue = this.props.queue;
return <span>{queue.length}</span>
}
}
class Foo extends Component{
static propTypes = {
cache:PropTypes.object
};
render(){
const cache = this.props.cache;
return (
<div>
<Bar queue={cache.queue}>hhh</Bar>
</div>
)
}
}
ReactDom.render(<Foo cache={store.cache}/>,document.querySelector('#root'));
当我们将store中的cache转化为可观察数据时,发现控制报错(传入Foo的属性类型不正确),我们是传入的array啊。
这里我们这里要使用mobx-react的PropTypes才可以。因为转换后的已经不是真正的数组了。
...
static propTypes = {
queue: ObservablePropTypes.observableArray
};
...
static propTypes = {
cache:ObservablePropTypes.observableObject
};
现在添加点击按钮使页面动起来
class Store {
@observable cache = {
queue:[]
}
@action.bound refresh(){
this.cache.queue.push(1);
}
}
@observer
class Bar extends Component{
static propTypes = {
queue: PropTypes.array
};
render(){
const queue = this.props.queue;
return <span>{queue.length}</span>
}
}
class Foo extends Component{
static propTypes = {
cache:PropTypes.object
};
render(){
const cache = this.props.cache;
return (
<div>
<button onClick={this.props.refresh}>refresh</button>
<Bar queue={cache.queue}/>
</div>
)
}
}
ReactDom.render(<Foo cache={store.cache} refresh={store.refresh}/>,document.querySelector('#root'));
总结:
mobx-react的observer就将组件的render方法包装为autorun,所以当可观察属性的改变的时候,会执行render方法。
当我把@observer放到Foo上面,而Ber删掉,会发现页面不会重新渲染。可以简单理解为autorun会对被引用的可观察属性做出反应, Foo中没有引用,所以不会重新渲染。
参考文章: