前言
- 提起 Mobx 想必大家已经很熟悉了,在它的官方介绍中有这么一句话叫做“MobX 是一个身经百战的库,它通过运用透明的函数式响应编程使状态管理变得简单和可扩展。”,今天这篇文章让我们一起探索下它的函数式响应编程到底是如何实现的。
- 借用官网的一张图我们可以看到 Mobx 是采用的单向数据流,利用 Action 改变 Observable State ,再通过执行 Reaction 副作用函数, 从而更新所有受影响的 View 。

- 我们重点从上方图片中提到的两个核心概念入手,一是 Observable State(响应式数据),二是 Reactions(响应式数据变更后执行的副作用函数)。
Observable State(响应式数据)
- 我们先从 Observable State 响应式数据入手,看下 Mobx 是如何将原始数据处理为响应式数据的。在 Mobx 中创建响应式数据的方式有很多,像
makeObservable
、makeAutoObservable
、observable
,但是他们实现逻辑是一致的。
- 现在我们以
observable
这个 api 作为入口示例,它支持将多种数据类型转换成为响应式数据,像原始数据类型、Object、Array、Map、Set 等,我们就以最常用的 Object 进行讲解,学习他们的底层实现方式。
observable 源码梳理
第一步,准备数据结构。
- 当我们调用
observable
传入一个原始对象后,Mobx 会先创建一个新的空对象(target)
- 基于 target 创建一个响应式对象管理器(ObservableObjectAdministration)
- target 会被赋值一个 $mobx 属性并指向 ObservableObjectAdministration
- ObservableObjectAdministration 中也会存在一个 target 属性指向 target
- 最后基于 target 创建一个 Proxy 对象,并将 Proxy 对象的 get & set 操作都会被代理到 ObservableObjectAdministration 上

function createObservable(v) {
if (isObject(v)) {
return object(v);
}
}
function object(v) {
const observableObject = asDynamicObservableObject({});
return observableObject;
}
const objectProxyTraps = {
get(target, name) {
return getAdm(target).get(name);
},
set(target, name, value) {
return getAdm(target).set(name, value);
},
};
function getAdm(target) {
return target[$mobx]
}
function asDynamicObservableObject(target) {
asObservableObject(target);
const proxy = new Proxy(target, objectProxyTraps);
return proxy;
}
const $mobx = Symbol("mobx administration")
let mobxGuid = 0;
function getNextId() {
return ++mobxGuid
}
function addHiddenProp(object, propName, value) {
Object.defineProperty(object, propName, {
enumerable: false,
writable: true,
configurable: true,
value
})
}
function asObservableObject(target) {
const name = `ObservableObject@${getNextId()}`;
const adm = new ObservableObjectAdministration(target, new Map(), name);
addHiddenProp(target, $mobx, adm);
return target;
}
class ObservableObjectAdministration {
constructor(target, values, name) {
this.target = target;
this.values = values;
this.name = name;
}
get(key) {
return this.target[key];
}
set(key, value) {
return (this.target[key] = value);
}
}
第二步,处理传入的原始数据。
- 处理我们在调用
observable
时传入的原始对象,生成响应式数据
- 遍历原始对象的每个属性,调用 adm.defineObservableProperty 对每个值进行处理
- 先是在 target 上赋值该 key ,值为一个 get 取值函数,最终会在 values 中进行取值
- 然后创建 ObservableValue 将真正的值存储到这个响应式对象中
- 最后把这个响应式对象存储到 values 这个 Map 里。

function object(v) {
const observableObject = asDynamicObservableObject({});
return extendObservable(observableObject, v);
}
function extendObservable(proxyObject, properties) {
const descriptors = Object.getOwnPropertyDescriptors(properties)
const adm = proxyObject[$mobx]
Reflect.ownKeys(descriptors).forEach(key => {
adm.extend(key, descriptors[key])
})
return proxyObject;
}
class ObservableObjectAdministration {
constructor(target, values, name) {
this.target = target;
this.values = values;
this.name = name;
}
get(key) {
return this.target[key];
}
set(key, value) {
return (this.target[key] = value);
}
extend(key, descriptor) {
this.defineObservableProperty(key, descriptor.value)
}
getObservablePropValue(key) {
return this.values.get(key).get()
}
setObservablePropValue(key, newValue) {
const observable = this.values.get(key)
observable.setNewValue(newValue)
return true;
}
defineObservableProperty(key, value) {
const descriptor = {
configurable: true,
enumerable: true,
get() {
return this[$mobx].getObservablePropValue(key);
},
set(value) {
return this[$mobx].setObservablePropValue(key, value);
},
};
Object.defineProperty(this.target, key, descriptor);
const observable = new ObservableValue(value);
this.values.set(key, observable);
}
}
export class ObservableValue {
constructor(value) {
this.value = value;
}
get() {
return this.value;
}
setNewValue(newValue) {
this.value = newValue
}
}
- 最终我们获取某个响应式数据的值时它的取值链条是这样的

Reaction(副作用函数)
- 响应式数据创建完成了,那么响应式数据发生变更后,某些函数便需要重新执行,这些就叫做副作用函数。接下来我们看下如何创建副作用函数以及数据变更后执行对应的副作用函数。
- 在 Mobx 我们可以通过
autorun
、reaction
、when
等 api 去创建副作用函数,也就是 Reaction 。这里我们以 autorun
这个 api 为示例去学习下这一步的实现逻辑。
autorun 源码梳理
第一步,依赖收集
- 想要实现数据变更便执行对应的副作用函数,首先要完成依赖收集
- Reaction 在第一次执行过程中会收集对应的 ObservableValue

- Reaction 执行完毕后 再由 ObservableValue 去收集对应的 Reaction

const globalState = {
pendingReactions: [],
trackingDerivation: null,
};
function autorun(view) {
const name = "Autorun@" + getNextId();
const reaction = new Reaction(
name,
function () {
this.track(view)
}
)
reaction.schedule()
}
class Reaction {
constructor(name = "Reaction@" + getNextId(), onInvalidate) {
this.name = name;
this.onInvalidate = onInvalidate;
this.observing = [];
}
track(fn) {
globalState.trackingDerivation = this
fn.call();
globalState.trackingDerivation = null;
bindDependencies(this)
}
schedule() {
globalState.pendingReactions.push(this)
runReactions()
}
runReaction() {
this.onInvalidate();
}
}
function bindDependencies(derivation) {
const { observing } = derivation;
observing.forEach(observable => {
observable.observers.add(derivation)
});
}
function runReactions() {
const allReactions = globalState.pendingReactions
let reaction;
while (reaction = allReactions.shift()) {
reaction.runReaction()
}
}
class ObservableValue {
constructor(value) {
this.value = value;
this.observers = new Set();
}
get() {
reportObserved(this)
return this.value;
}
}
function reportObserved(observable) {
const derivation = globalState.trackingDerivation
if (derivation !== null) {
derivation.observing.push(observable);
}
}
第二步,通知更新
- 依赖收集完成后, 修改响应式数据时如何通知对应的 Reaction 执行就简单了
- 在 ObservableValue 的 observers 拿到对应的 Reaction 执行它的 runReaction 即可
class ObservableValue {
constructor(value) {
this.value = value;
this.observers = new Set();
}
get() {
reportObserved(this)
return this.value;
}
setNewValue(newValue) {
this.value = newValue;
propagateChanged(this)
}
}
function propagateChanged(observable) {
const observers = observable.observers;
observers.forEach(observer => {
observer.onBecomeStale()
})
}
class Reaction {
constructor(name = "Reaction@" + getNextId(), onInvalidate) {
this.name = name;
this.onInvalidate = onInvalidate;
this.observing = [];
}
schedule() {
globalState.pendingReactions.push(this)
runReactions()
}
runReaction() {
this.onInvalidate();
}
onBecomeStale() {
this.schedule()
}
}
function runReactions() {
const allReactions = globalState.pendingReactions
let reaction;
while (reaction = allReactions.shift()) {
reaction.runReaction()
}
}
Actions & Derived values(动作以及派生状态)
- 由于本文主要探究的是 Mobx 响应式的核心实现,Action 及 Derived Values 并不属于响应式核心,所以只大致介绍下其在 Mobx 中起到的作用,具体如下。
- Action 称之为动作,Mobx 规定修改响应式数据必须通过 Action 来修改。Action 实际上在 Mobx 中的作用为通过 startBatch、endBatch 事务封装执行动作,将多次响应式状态变更复合为一,等多次状态变更完成后才会执行后续 Reaction ,避免了多次状态变更导致多次副作用函数无意义执行,优化了性能。

- Derived values 称为派生状态,当 State 数据发生变更后会通过某些计算方式由变更后的 State 派生出新数据,进而再去执行 Reactions 副作用函数,这也就是我们都熟悉的 Computed values 。本文并未对此部分做过多阐述,只重点关注了当 State 变更后直接就需要自动运行的 Reactions(副作用)即下图中的第二部分。

总结
- 以上就是这篇文章的全部内容了,希望通过这篇文章可以帮助大家对 Mobx 的响应式做一个入门,让大家清楚在 Mobx 里是如何创建响应式对象的,如何进行依赖收集的,数据变更后如何执行副作用函数的。
- 其实这篇文章提到的内容仅仅是 Mobx 里的冰山一角,感兴趣的同学可以借此机会深入了解下 Mobx 的源码, 学习下其他数据结构的处理,Mobx 的事务的处理等等,它还有很多矿藏等你去发掘。