mobx 的原理以及在 React 中应用
mobx 和 redux 都是用来管理 react 中数据状态,为什么要引入呢,考虑到一下数据状态传递情况:
-
父子组件的传递
-
兄弟组件之间传递
-
跨层级组件之间传递
总之,为了实现这些情况下状态共享,社区出现了 redux 以及 mobx 等诸多优秀的方案,一般来说,redux 是一个设计规范、严格的单向数据流框架,适用于大型项目。而 mobx 一种更灵活的、适合于中小型应用的数据层框架。
原理:
讲原理前,可能得回顾一下,观察者模式,装饰者模式
观察者模式
可能会联想到常见的 jQuery 中 on,window.addEventListener,它们是属于发布/订阅模式( 多了事件通道的观察者模式 ), 具体的代码
-
将系统分割成一系列相互协作的类,
-
减少了类紧密耦合
-
一个目标有任意数目与之相依赖的观察者,一旦目标状态改变,它的观察者们都将得到通知
装饰者模式
这个可以查看 阮老师的书 关于最简单的 es7 装饰器实现 装饰者模式 。注意,装饰器只能用于类以及类的方法
开始介绍
接下来通过 一个简单的例子 了解 mobx
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
import { observable, autorun } from "mobx";
const counter = observable({
count: 0
});
window.counter = counter;
autorun(() => {
console.log("#count", counter.count);
});
function App() {
return (
<div className="App">
<h1>Hello mobx</h1>
<button
onClick={e => {
counter.count++;
}}
>
click +1
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
-
执行传入函数,计算出 observing
- 怎么计算? 访问数据时会走到 Observable 的 get() 方法,通过 get() 方法做的记录
-
在 observing 的 Observable 的 observer 里添加这个 Derivation
到这里,Observable 和 Derivation 的依赖关联就建立起来了。
那么 counter.set()
执行之后是如何触发 autorun
自动执行? 在有了上面这一层依赖关系之后,这个就很好理解了。 counter.set()
执行时会从自己的 observing 属性里取依赖他的 Derivation,并触发他们的重新执行。
运行时依赖计算
再看一个例子。
import { observable, autorun } from 'mobx';
const counter = observable(0);
const foo = observable(0);
const bar = observable(0);
autorun(() => {
if (counter.get() === 0) {
console.log('foo', foo.get());
} else {
console.log('bar', bar.get());
}
});
bar.set(10); // 不触发 autorun
counter.set(1); // 触发 autorun
foo.set(100); // 不触发 autorun
bar.set(100); // 触发 autorun
执行结果:
foo 0
bar 10
bar 100
autorun 先是依赖 counter 和 foo,然后 counter 设为 1 之后,就不依赖 foo,而是依赖 counter 和 bar 了。所以之后修改 foo 并不会触发 autorun 。
那么 mobx 是如何在运行时计算依赖的呢?
实际上前面的 autorun
的执行步骤是做了简化的,真实的是这样:
-
生成一个 Derivation
-
记录 oldObserving
(+)
-
执行传入函数,计算出 observing
- 怎么计算? 访问数据时会走到 Observable 的 get() 方法,通过 get() 方法做的记录
-
和 oldObserving 做 diff,得到新增和删除列表
(+)
-
通过前面得到的 diff 结果,修改 Observable 的 observing
相比之前的,增加了 diff 的逻辑,以达到每次执行的时候动态更新依赖关系表的目的。
get/set magic
大家在看前面的例子里可能会有个疑问,为啥第一个例子里可以通过 appState.counter
来设置,而后面的例子里需要用 counter.get
和 counter.set
来取值和设值?
这和数据类型有关,mobx 支持的类型有 primitives, arrays, classes 和 objects 。primitives (原始类型) 只能通过 set 和 get 方法取值和设值。而 Object 则可以利用 Object.defineProperty
方法自定义 getter 和 setter 。
Object.defineProperty(adm.target, propName, {
get: function() { return observable.get(); },
set: ...
});
详见 源码 。
ComputedValue
ComputedValue 同时实现了 Observable 和 Derivation 的接口,即可以监听 Observable,也可以被 Derivation 监听。
Reaction
Reaction 本质上是 Derivation,但他不能再被其他 Derivation 监听。
Autorun
autorun 是 Reaction 的 简单封装 。
同步执行
其他的 TFRP 类库,比如 Tracker 和 Knockout ,数据更新后的执行都是异步的,需要等到下一个 event loop 。(可以想象成 setTimeout) 而 Mobx 的执行是同步的,这样做有两个好处:
-
ComputedValue 在他依赖的值修改后可以马上被使用,这样你就永远不会使用一个过期的 ComputedValue
-
调试方便,堆栈里没有冗余的 Promise / async 库
Transation
由于 mobx 的更新是同步的,所以每 set 一个值,就会触发 reaction 的更新。所以为了批量更新,就引入了 transation 。
transaction(() => {
user.firstName = "foo";
user.lastName = "bar";
});
"babel": {
"plugins": [
"transform-decorators-legacy"
],
"presets": [
"react-app"
]
},
使用 mobx 也可以不采用装饰器,具体看 这里
应用:
查看 cn.mobx.js.org/ 更加详细的 api 在项目中使用: codesandbox.io/s/mobxjutid…
import { observable } from "mobx";
import { observer } from "mobx-react";
import React, { Component } from "react";
import ReactDOM from "react-dom";
export const store = observable({
count: 0
});
store.increment = function() {
this.count++;
};
store.decrement = function() {
this.count--;
};
@observer
class Count extends Component {
render() {
return (
<div>
Counter: {store.count} <br />
<button onClick={this.handleInc}> + </button>
<button onClick={this.handleDec}> - </button>
</div>
);
}
handleInc() {
store.increment();
}
handleDec() {
store.decrement();
}
}
ReactDOM.render(<Count />, document.querySelector("#root"));
mobx 定义了 store,Count 的 render 执行时里引用 store 的数据。然后如果用户点击 + 或 - 按钮,会触发 store 的修改,store 的修改会自动触发 Counter 的更新。相当于 store 就是一个共享的数据状态,可以供多个
一个mobx 实现 todo 的 例子
Mobx 和 Redux 的比较
-
Redux 认为,数据的一致性很重要,为了保持数据的一致性,要求Store 中的数据尽量范式化,也就是减少一切不必要的冗余,为了限制对数据的修改,要求 Store 中数据是不可改的(Immutable),只能通过 action 触发 reducer 来更新 Store。
-
Mobx 也认为数据的一致性很重要,但是它认为解决问题的根本方法不是让数据范式化,而是不要给机会让数据变得不一致。所以,Mobx 鼓励数据干脆就“反范式化”,有冗余没问题,只要所有数据之间保持联动,改了一处,对应依赖这处的数据自动更新,那就不会发生数据不一致的问题。
虽然 Mobx 最初的一个卖点就是直接修改数据,但是实践中大家还是发现这样无组织无纪律不好,所以后来 Mobx 还是提供了 action 的概念。和 Redux 的 action 有点不同,Mobx 中的 action 其实就是一个函数,不需要做 dispatch
,调用就修改对应数据,在上面的代码中, increment
和 decrement
就是 action。
如果想强制要求使用 action,禁止直接修改 observable 数据,使用 Mobx 的 configure
,如下:
import {configure} from 'mobx';
configure({enforceActions: true});
总结一下 Redux 和 Mobx 的区别,包括这些方面:
-
Redux 鼓励一个应用只用一个 Store,Mobx 鼓励使用多个 Store;
-
Redux 使用“拉”的方式使用数据,这一点和 React是一致的,但 Mobx 使用“推”的方式使用数据,和 RxJS 这样的工具走得更近;
-
Redux 鼓励数据范式化,减少冗余,Mobx 容许数据冗余,但同样能保持数据一致。
扩展:函数式编程
参考:
转载自:https://juejin.cn/post/6979127131104083976