mobx

概念

MobX 是响应式编程,实现状态的存储和管理。使用 MobX 将应用变成响应式可归纳为三部 曲:

  • 定义状态并使其可观察
  • 创建视图以响应状态的变化
  • 更改状态
    1. observable 是将类属性等进行标记,实现对其的观察。三部曲中的第一曲,就是通过 Observable 实现的。
    2. 通过 action 改变 state。三部曲中的第一曲通过 action 创建一个动作。action 函数是对传入的 function 进行一次包装,使得 function 中的 observable 对象的变化能够被观察到,从而触发相应的衍生。

响应式

mobx 和 vue 一样的数据监听,底层通过 Object.defineProperty 或 Proxy 来劫持数据,可以做到更细粒度的渲染。
在 react 中反而把更新组件的操作(setState)交给了使用者,由于 setState 的”异步”特性导致了没法立刻拿到更新后的 state。

依赖收集

在 mobx 中,通过 autorun 和 reaction 对依赖的数据进行了收集(可以通过 get 来收集),一旦这些数据发生了变化,就会执行接受到的函数,和发布订阅很相似。
mobx-react 中则提供了 observer 方法,用来收集组件依赖的数据,一旦这些数据变化,就会触发组件的重新渲染。

管理局部状态

在 react 中,我们更新状态需要使用 setState,但是 setState 后并不能立马拿到更新后的 state,虽然 setState 提供了一个回调函数,我们也可以用 Promise 来包一层,但终究还是个异步的方式。
在 mobx 中,我们可以直接在 react 的 class 里面用 observable 声明属性来代替 state,这样可以立马拿到更新后的值,而且 observer 会做一些优化,避免了频繁 render。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@observer
class App extends React.Component {
@observable count = 0;
constructor(props) {
super(props);
}
@action
componentDidMount() {
this.count = 1;
this.count = 2;
this.count = 3;
}
render() {
return <h1>{this.count}</h1>;
}
}

react-mobx

computed

计算值(computed values)是可以根据现有的状态或其它计算值衍生出的值。简单理解为对可观察数据做出的反应,多个可观察属性进行处理,然后返回一个可观察属性使用方式:1、作为普通函数,2、作为 decorator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 的参数修改的时候会触发自动运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

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(自动运行程序) 会被清理。
该函数返回一个清理器以提前取消自动运行程序。
when 方法接收两个参数(两个方法),第一个参数根据可观察属性的值做出判断
返回一个 boolean 值,当为 true 的时候,执行第二个参数。如果一开始就返回
一个 true,就会立即执行后面的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;

computed

computed 是基于现有状态或计算值衍生出的值
如下面 todoList 的例子,一旦已完成事项数量改变,那么 completedCount 会自动更新。

1
2
3
4
5
6
class TodoStore {
@observable todos = [];
@computed get completedCount() {
return (this.todos.filter((todo) => todo.isCompleted) || []).length;
}
}

reaction

reaction 则是和 autorun 功能类似,但是 autorun 会立即执行一次,而 reaction 不会,使用 reaction 可以在监听到指定数据变化的时候执行一些操作,有利于和副作用代码解耦。

1
2
3
4
5
6
// 当todos改变的时候将其存入缓存
reaction(
() => toJS(this.todos),
(todos) =>
localStorage.setItem("mobx-react-todomvc-todos", JSON.stringify({ todos }))
);

用法:reaction(() => data, (data, reaction) => { sideEffect }, options?)
它接收两个函数参数,第一个(数据函数)是用来追踪并返回数据作为第二个函数(效果函数)的输入。不同于 autorun 的是当创建时效果函数不会直接运行(第二个参数不会立即执行, autorun 会立即执行传入的参数方法),只有在数据表达式首次返回一个新值后才会运行。在执行效果函数时访问的任何 observable 都不会被追踪。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;

action:

在 redux 中,唯一可以更改 state 的途径便是 dispatch 一个 action。这种
约束性带来的一个好处是可维护性。整个 state 只要改变必定是通过 action 触发的,对此只要找到 reducer 中对应的 action 便能找到影响数据改变的原因。强约束性是好
的,但是 Redux 要达到约束性的目的,似乎要写许多样板代码,虽说有许多库都在解决该问题,然而 Mobx 从根本上来说会更加优雅。 首先 Mobx 并不强制所有 state 的改变必须通过 action 来改变,这主要适用于一些较小的项目。对于较大型的,需要多人合作的项目来说,可以使用 Mobx 提供的 api configure 来强制。

observer

mobx-react 的 observer 就将组件的 render 方法包装为 autorun,所以当可观察属性的改变的时候,会执行 render 方法。

observable

observable 是一种让数据的变化可以被观察的方法
observable(value) 是一个便捷的 API ,此 API 只有在它可以被制作成可观察的数据结构(数组、映射或 observable 对象)时才会成功。对于所有其他值,不会执行转换。

项目工程化


mobx 中的 store 的创建偏向于面向对象的形式
action 和 dataModel 一起组合成了页面的总 store,dataModel 只存放 UI 数据以及只涉及自身数据变化的 action 操作(在 mobx 严格模式中,修改数据一定要用 action 或 flow)。
action store 则是负责存放一些需要使用来自不同 store 数据的 action 操作。
dataModel 更像 MVC 中的 model,action store 是 controller,react components 则是 view,三者构成了 mvc 的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
-stores -
actions -
hotelListAction.js -
dataModel -
globalStatus.js -
hotelList.js -
index.js;
// globalStatus

class GlobalStatus {
@observable isShowLoading = false;
@action showLoading = () => {
this.isShowLoading = true;
};
@action hideLoading = () => {
this.isShowLoading = false;
};
}
// hotelList
class HotelList {
@observable hotels = [];
@action addHotels = (hotels) => {
this.hotels = [...toJS(this.hotels), ...hotels];
};
}
// hotelListAction
class HotelListAction {
fetchHotelList = flow(function* () {
const { globalStatus, hotelList } = this.rootStore;
globalStatus.showLoading();
try {
const res = yield fetch("/hoteList", params);
hotelList.addHotels(res.hotels);
} catch (err) {
} finally {
globalStatus.hideLoading();
}
}).bind(this);
}