logo

MobX 核心源码解析

前言

在上一篇《高效的 MobX》中,主要讲解了 MobX 的使用方法,以及其几个核心概念:Observable State、computed Value、Reactions、Actions

了解了其使用方法(API)以及其数据流之后,更重要的是揭开其底层的核心原理。在这一篇中,将深入 MobX 源码来解析其核心原理以及工作流程。

mobx-core

TIP

在本篇文章中,有几个需要注意的点:

  • 由于 MobX 的代码量较大,因此很多地方只能部分(会说明其在源码中的位置)截取说明;

  • 在源码中会大量出现 invariant 这个函数,它是在开发环境中输出一些 info/warn 信息的,因此这些部分将会省略;

  • 阅读的 MobX 源码版本为 @3.2.1 ,使用的是 TypeSript ,因此对于 TypeSript 不熟悉的同学可以提前了解;


observable

在 MobX 中可以使用 @observable 来实现一个类中属性的可观察化:

class Store {
id = Math.random()
@observable data = []
}

而该写法使用了修饰器,其等同于:

function Store() {
this.id = Math.random()
extendObservable(this, {
data: []
})
}

因此可以说 observable 实质是 extendObservable 函数的语法糖🍬形式。

接下来具体从 observable 为入口分析 MobX 底层是如何实现一个属性的可观察化。

从代码的入口文件 ./src/mobx.ts 中可以看到,extendObservable 是来自 ./src/api/extendobservable.ts

export function extendObservable<A extends Object, B extends Object>(target: A, ...properties: B[]): A & B {
return extendObservableHelper(target, deepEnhancer, properties) as any;
}

// ...

export function extendObservableHelper(target: Object, defaultEnhancer: IEnhancer<any>, properties: Object[]): Object {
// ...
// 第一步
const adm = asObservableObject(target);
const definedProps = {};
// 第二步
for (let i = properties.length - 1; i >= 0; i--) {
const propSet = properties[i];
for (let key in propSet) if (definedProps[key] !== true && hasOwnProperty(propSet, key)) {
definedProps[key] = true;
if (target as any === propSet && !isPropertyConfigurable(target, key))
continue;
const descriptor = Object.getOwnPropertyDescriptor(propSet, key);
// 第三步
defineObservablePropertyFromDescriptor(adm, key, descriptor, defaultEnhancer);
}
}
return target;
}

extendObservable 函数第一个参数就是目标对象(target);
后面的参数不定数目不定,都为对象(包含了需要添加到目标对象中可观察属性),比如:

extendObservableHelper(object, { name: 'n' }, { age: 12 })

传入的 { name: 'n' }{ age: 12 },则 nameage 两个属性都会设置给 object 对象,并使其可观察化;

然后通过扩展运算 ... 将后面的不定参数变成数组形式,之后调用 extendObservableHelper 函数;

在这里,分为三个步骤:

第一步:

通过 asObservableObject 得到一个 adm

(src/types/observableobject.ts)

export function asObservableObject(target, name?: string): ObservableObjectAdministration {
if (isObservableObject(target))
return (target as any).$mobx;

if (!isPlainObject(target))
name = (target.constructor.name || "ObservableObject") + "@" + getNextId();
if (!name)
name = "ObservableObject@" + getNextId();

const adm = new ObservableObjectAdministration(target, name);
addHiddenFinalProp(target, "$mobx", adm);
return adm;
}

asObservableObject 函数中,主要所做的就是给目标对象设置一个 $mobx 属性作为其代理对象,以实现对目标对象的可观察化,最后返回其代理对象;

首先,通过 isObservableObject 判断其是否以及被可观察化处理,在初始化的时候显然还未被处理;

接着,生成一个唯一性的 name 参数;

之后通过 ObservableObjectAdministration 类实例化出一个对象作为其代理对象;

src/types/observableobject.ts

export class ObservableObjectAdministration implements IInterceptable<IObjectWillChange>, IListenable {
values: {[key: string]: ObservableValue<any>|ComputedValue<any>} = {};
changeListeners = null;
interceptors = null;

constructor(public target: any, public name: string) { }

observe(callback: (changes: IObjectChange) => void, fireImmediately?: boolean): Lambda {
invariant(fireImmediately !== true, "`observe` doesn't support the fire immediately property for observable objects.");
return registerListener(this, callback);
}

intercept(handler): Lambda {
return registerInterceptor(this, handler);
}
}

它有 values 属性,是用来存储可观察属性对象的;
同时在实例化的时候会赋予一个 target 属性(目标对象),这就保证了在代理对象中可以获取到其代理的目标对象;

然后在 addHiddenFinalProp 函数中:

src/utils/utils.ts

export function addHiddenFinalProp(object: any, propName: string, value: any) {
Object.defineProperty(object, propName, {
enumerable: false,
writable: false,
configurable: true,
value
});
}

在该函数中,通过 Object.defineProperty 方法给目标对象添加了一个 $mobx(不可写、不可枚举)的属性,其值则是 ObservableObjectAdministration 的实例对象,因此可以通过 $mobx 属性获取到目标对象的代理对象;

最后,将其代理对象返回;

因此,可以得出结论,将一个普通的目标对象转化为可观察的对象即是通过给它添加一个 $mobx 属性,其值为一个 ObservableObjectAdministration 类,因此对其的可观察化不会对该对象本身产生影响,对其的所有操作都将通过其 $mobx 属性代理。

在后文,都将 target.$mobx 描述为代理对象;

第二步:

在这一步中,会遍历所有需要添加到目标对象中的可观察属性,通过 hasOwnPropertyisPropertyConfigurable 并且判断该属性值时候引用了目标对象本身来过滤掉过一些属性;
通过 Object.getOwnPropertyDescriptor 获取到属性描述;

关于属性描述可以看:(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor)[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor]

第三步:

在第三步中调用了 defineObservablePropertyFromDescriptor 函数:

从该函数名应该可以猜想到它的作用就是通过属性描述定义可观察属性;

src/types/observableobject.ts

export function defineObservablePropertyFromDescriptor(adm: ObservableObjectAdministration, propName: string, descriptor: PropertyDescriptor, defaultEnhancer: IEnhancer<any>) {
if (adm.values[propName]) {
adm.target[propName] = descriptor.value;
return;
}

if ("value" in descriptor) {
if (isModifierDescriptor(descriptor.value)) {
const modifierDescriptor = descriptor.value as IModifierDescriptor<any>;
defineObservableProperty(adm, propName, modifierDescriptor.initialValue, modifierDescriptor.enhancer);
}
else if (isAction(descriptor.value) && descriptor.value.autoBind === true) {
defineBoundAction(adm.target, propName, descriptor.value.originalFn);
} else if (isComputedValue(descriptor.value)) {
defineComputedPropertyFromComputedValue(adm, propName, descriptor.value);
} else {
defineObservableProperty(adm, propName, descriptor.value, defaultEnhancer);
}
} else {
defineComputedProperty(adm, propName, descriptor.get, descriptor.set, comparer.default, true);
}
}

在这里,adm 就是前文中的代理属性,首先判断它的 values 中是否存在了该属性(已经被可观察化), 若已存在该属性,则直接赋值返回,初始化的时候显然是不存在的;
而后判断其是 可计算属性、action,还是可观察属性,在初始化时,这里应该调用的是 defineObservableProperty函数:

src/types/observableobject.ts

export function defineObservableProperty(
adm: ObservableObjectAdministration,
propName: string,
newValue,
enhancer: IEnhancer<any>
) {
...
if (hasInterceptors(adm)) {
const change = interceptChange<IObjectWillChange>(adm, {
object: adm.target,
name: propName,
type: "add",
newValue
});
if (!change)
return;
newValue = change.newValue;
}
const observable = adm.values[propName] = new ObservableValue(newValue, enhancer, `${adm.name}.${propName}`, false);
newValue = (observable as any).value;

Object.defineProperty(adm.target, propName, generateObservablePropConfig(propName));
notifyPropertyAddition(adm, adm.target, propName, newValue);
}

首先判断 adm(代理对象)是否有拦截器,在这里是暂时没有的(初始化 ObservableObjectAdministrationinterceptors 属性为 null),因此跳过这部分;

之后实例化一个 ObservableValue 对象并将其作为代理对象中 values 对象中的一个属性值(adm.values[propName]);

这里看看 ObservableValue 类:

src/types/observablevalue.ts

export class ObservableValue<T> extends BaseAtom implements IObservableValue<T>, IInterceptable<IValueWillChange<T>>, IListenable {
hasUnreportedChange = false;
interceptors;
changeListeners;
protected value;
dehancer: any = undefined;

constructor(value: T, protected enhancer: IEnhancer<T>, name = "ObservableValue@" + getNextId(), notifySpy = true) {
super(name);
this.value = enhancer(value, undefined, name);
if (notifySpy && isSpyEnabled()) {
// only notify spy if this is a stand-alone observable
spyReport({ type: "create", object: this, newValue: this.value });
}
}

...

public set(newValue: T) {
const oldValue = this.value;
newValue = this.prepareNewValue(newValue) as any;
if (newValue !== UNCHANGED) {
const notifySpy = isSpyEnabled();
if (notifySpy) {
spyReportStart({
type: "update",
object: this,
newValue, oldValue
});
}
this.setNewValue(newValue);
if (notifySpy)
spyReportEnd();
}
}

...

public get(): T {
this.reportObserved();
return this.dehanceValue(this.value);
}
...
}

ObservableValue 类非常复杂,它继承了 BaseAtom 类,且具有他自己的一些值与方法;
这里注意一点的是实例化该它的时候其 name 属性为传入的 ${adm.name}.${propName},同时它的 value 属性会通过 enhancer(前文传入的 deepEnhancer) 方法处理;

回到 defineObservableProperty 中,之后通过 Object.defineProperty 方法给目标对象中的可观察属性设置属性描述;

而属性描述则是通过 generateObservablePropConfig 函数生成:

src/types/observableobject.ts

export function generateObservablePropConfig(propName) {
return observablePropertyConfigs[propName] || (
observablePropertyConfigs[propName] = {
configurable: true,
enumerable: true,
get: function() {
return this.$mobx.values[propName].get();
},
set: function(v) {
setPropertyValue(this, propName, v);
}
}
);
}

正如前文的猜想,在属性描述中重写了其对象的 settergetter,对目标对象中的可观察属性进行读写操作的时候都会代理到 target.$mobx 代理对象中进行操作;

这里具体的 getset 如何代理到 target.$mobx 中将在后文阐述

到这里为止,简单的说就是当给目标函数添加一系列可观察属性的时候,会先给目标函数生成一个 $mobx 属性作为其代理对象,而后将每个属性值转化为一个 ObservableValue 对象,并存储在 $mobx.value[propName] 中,最后给目标对象设置每个可观察属性的属性描述,通过重写 getset 函数来将外界对目标对象的读写操作代理到其代理对象。

autorun

初始化可观察对象之后,则需要执行 autorun ,同时传入监听函数以监听可观察属性的变化;

autorun 中所做的工作则是初始化执行监听函数,收集依赖,进行依赖绑定;

直接来看 autorun 函数:

src/api/autorun.ts

export function autorun(arg1: any, arg2: any, arg3?: any) {
let name: string,
view: (r: IReactionPublic) => any,
scope: any;

if (typeof arg1 === "string") {
name = arg1;
view = arg2;
scope = arg3;
} else {
name = arg1.name || ("Autorun@" + getNextId());
view = arg1;
scope = arg2;
}

...
if (scope)
view = view.bind(scope);

const reaction = new Reaction(name, function () {
this.track(reactionRunner);
});

function reactionRunner() {
view(reaction);
}

reaction.schedule();

return reaction.getDisposer();
}

使用 autorun 函数可以传入监听函数、作用域、名字,在一开始初始化判断操作之后,可以看出 view 既是需要的监听函数;

在这里函数的执行步骤及其曲折;

第一步:

首先是获得一个 Reaction 实例对象:

src/core/reaction.ts

export class Reaction implements IDerivation, IReactionPublic {
observing: IObservable[] = [];
newObserving: IObservable[] = [];
dependenciesState = IDerivationState.NOT_TRACKING;
diffValue = 0;
runId = 0;
unboundDepsCount = 0;
__mapid = "#" + getNextId();
isDisposed = false;
_isScheduled = false;
_isTrackPending = false;
_isRunning = false;
errorHandler: (error: any, derivation: IDerivation) => void;

constructor(public name: string = "Reaction@" + getNextId(), private onInvalidate: () => void) { }

onBecomeStale() {
this.schedule();
}

schedule() {
if (!this._isScheduled) {
this._isScheduled = true;
globalState.pendingReactions.push(this);
runReactions();
}
}

isScheduled() {
return this._isScheduled;
}

runReaction() {
if (!this.isDisposed) {
startBatch();
this._isScheduled = false;
if (shouldCompute(this)) {
this._isTrackPending = true;

this.onInvalidate();
if (this._isTrackPending && isSpyEnabled()) {
spyReport({
object: this,
type: "scheduled-reaction"
});
}
}
endBatch();
}
}

track(fn: () => void) {
startBatch();
const notify = isSpyEnabled();
let startTime;
if (notify) {
startTime = Date.now();
spyReportStart({
object: this,
type: "reaction",
fn
});
}
this._isRunning = true;
const result = trackDerivedFunction(this, fn, undefined);
this._isRunning = false;
this._isTrackPending = false;
if (this.isDisposed) {
clearObserving(this);
}
if (isCaughtException(result))
this.reportExceptionInDerivation(result.cause);
if (notify) {
spyReportEnd({
time: Date.now() - startTime
});
}
endBatch();
}

reportExceptionInDerivation(error: any) {
if (this.errorHandler) {
this.errorHandler(error, this);
return;
}

const message = `[mobx] Encountered an uncaught exception that was thrown by a reaction or observer component, in: '${this}`;
const messageToUser = getMessage("m037");

console.error(message || messageToUser, error);

if (isSpyEnabled()) {
spyReport({
type: "error",
message,
error,
object: this
});
}

globalState.globalReactionErrorHandlers.forEach(f => f(error, this));
}

dispose() {
if (!this.isDisposed) {
this.isDisposed = true;
if (!this._isRunning) {
startBatch();
clearObserving(this);
endBatch();
}
}
}

getDisposer(): IReactionDisposer {
const r = this.dispose.bind(this);
r.$mobx = this;
r.onError = registerErrorHandler;
return r;
}
...
}

Reaction 类也是非常恶心的,初始化的时候会传入 nameonInvalidate 属性;

第二步:

调用 reaction.schedule() 方法,在该方法中,先是向 globalState.pendingReactions 中增加了其自身(Reaction 实例)(globalState 是用来在 MobX 内部生命周期内存储各种全局执行状态的一个对象),然后调用 runReactions() 函数;

第三步:

runReactions 函数中执行了 reactionScheduler 函数,在 reactionScheduler 执行了其参数 runReactionsHelper

src/core/reaction.ts

let reactionScheduler: (fn: () => void) => void = f => f();

export function runReactions() {
// Trampolining, if runReactions are already running, new reactions will be picked up
if (globalState.inBatch > 0 || globalState.isRunningReactions)
return;
reactionScheduler(runReactionsHelper);
}
第四步:

runReactionsHelper 函数中:

src/core/reaction.ts

function runReactionsHelper() {
globalState.isRunningReactions = true;
const allReactions = globalState.pendingReactions;
let iterations = 0;

while (allReactions.length > 0) {
if (++iterations === MAX_REACTION_ITERATIONS) {
console.error(`Reaction doesn't converge to a stable state after ${MAX_REACTION_ITERATIONS} iterations.`
+ ` Probably there is a cycle in the reactive function: ${allReactions[0]}`);
allReactions.splice(0);
}
let remainingReactions = allReactions.splice(0);
for (let i = 0, l = remainingReactions.length; i < l; i++)
remainingReactions[i].runReaction();
}
globalState.isRunningReactions = false;
}

会遍历 globalState.pendingReactions 中的所有 Reaction 对象,在这里就是在 第二步 中传入的 Reaction 对象,并执行 Reaction.runReaction 方法;

第五步:

再到 Reaction 类中看看 runReaction 方法:

src/core/reaction.ts

// ...
runReaction() {
if (!this.isDisposed) {
startBatch();
this._isScheduled = false;
if (shouldCompute(this)) {
this._isTrackPending = true;

this.onInvalidate();
if (this._isTrackPending && isSpyEnabled()) {
spyReport({
object: this,
type: "scheduled-reaction"
});
}
}
endBatch();
}
}
// ...

在这一步中,核心是执行了 this.onInvalidate() 方法,该方法则是实例化 Reaction 对象时候传入的第二个参数,是在 autorun 中所定义的:

src/api/autorun.ts

// ...
const reaction = new Reaction(name, function () {
this.track(reactionRunner);
});

// ...

function reactionRunner() {
view(reaction);
}

在该方法中,调用了 reaction.track 方法,并传入一个 reactionRunner 函数:

第六步:

这里再分析下 reaction.track 方法,在 Reaction 类中:

src/core/reaction.ts

// ...
track(fn: () => void) {
startBatch();
const notify = isSpyEnabled();
let startTime;
if (notify) {
startTime = Date.now();
spyReportStart({
object: this,
type: "reaction",
fn
});
}
this._isRunning = true;
const result = trackDerivedFunction(this, fn, undefined);
this._isRunning = false;
this._isTrackPending = false;
if (this.isDisposed) {
// disposed during last run. Clean up everything that was bound after the dispose call.
clearObserving(this);
}
if (isCaughtException(result))
this.reportExceptionInDerivation(result.cause);
if (notify) {
spyReportEnd({
time: Date.now() - startTime
});
}
endBatch();
}
// ...

这里,其主要是执行了 trackDerivedFunction(this, fn, undefined); 方法;

第七步:

trackDerivedFunction 函数中:

src/core/derivation.ts

export function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context) {
changeDependenciesStateTo0(derivation);
derivation.newObserving = new Array(derivation.observing.length + 100);
derivation.unboundDepsCount = 0;
derivation.runId = ++globalState.runId;
const prevTracking = globalState.trackingDerivation;
globalState.trackingDerivation = derivation;
let result;
try {
result = f.call(context);
} catch (e) {
result = new CaughtException(e);
}
globalState.trackingDerivation = prevTracking;
bindDependencies(derivation);
return result;
}

在该函数中,先是将 globalState.trackingDerivation 设为当前运行的 IDerivation,也就是上述中的 Reaction
之后调用 f ,该方法就是 autorun 中的 view 方法,因此,在初始化 autorun 的时候,它会立即执行一遍传入的监听函数,而在执行该函数的时候就是对于这一次 Reaction 中所依赖的可观察属性的收集,既是保证了每个 autorun 的观察函数只会在其内部依赖的可观察属性发生改变的时候得到响应。

而对于之后的 bindDependencies 如何实现依赖的绑定则在后文解析。

依赖绑定

第一步:

在上一步中,到了初始化执行 autorun 中的监听函数这一步骤,而去收集依赖的方法就是通过执行观察函数的时候,如果依赖一个可观察属性就会读取其值,也就是会触发其属性描述中的 get 函数;

在这里再次回到 extendObservable 的后文,在 extendObservable 的操作过程中,最后代理了目标函数的中可观察属性的 gettersetter

src/types/observableobject.ts

export function generateObservablePropConfig(propName) {
return observablePropertyConfigs[propName] || (
observablePropertyConfigs[propName] = {
configurable: true,
enumerable: true,
get: function() {
return this.$mobx.values[propName].get();
},
set: function(v) {
setPropertyValue(this, propName, v);
}
}
);
}

在读取对象的可观察属性的时候,触发 set 函数,执行 this.$mobx.values[propName].get() 并返回,也就是从它的代理对象的 values 中获取到该属性(ObservableValue 实例对象),并执行 get; 方法返回其真正值

这里再来看看 ObservableValueget 方法:

src/types/observablevalue.ts

export class ObservableValue<T> extends BaseAtom implements IObservableValue<T>, IInterceptable<IValueWillChange<T>>, IListenable {
// ...
public set(newValue: T) {
const oldValue = this.value;
newValue = this.prepareNewValue(newValue) as any;
if (newValue !== UNCHANGED) {
const notifySpy = isSpyEnabled();
if (notifySpy) {
spyReportStart({
type: "update",
object: this,
newValue, oldValue
});
}
this.setNewValue(newValue);
if (notifySpy)
spyReportEnd();
}
}

setNewValue(newValue: T) {
const oldValue = this.value;
this.value = newValue;
this.reportChanged();
if (hasListeners(this)) {
notifyListeners(this, {
type: "update",
object: this,
newValue, oldValue
});
}
}

public get(): T {
this.reportObserved();
return this.dehanceValue(this.value);
}
// ...
}

它先是调用了 this.reportObserved,之后返回了处理之后的值;

ObservableValuereportObserved 方法并不在其本身,而是继承 BaseAtom 类获得:

src/core/atom.ts

export class BaseAtom implements IAtom {
isPendingUnobservation = true;
observers = [];
observersIndexes = {};

diffValue = 0;
lastAccessedBy = 0;
lowestObserverState = IDerivationState.NOT_TRACKING;

constructor(public name = "Atom@" + getNextId()) { }

public onBecomeUnobserved() {
// noop
}

public reportObserved() {
reportObserved(this);
}

public reportChanged() {
startBatch();
propagateChanged(this);
endBatch();
}
// ...
}

在执行该对象方法的时候调用了外部方法 reportObserved 并传入其对象本身(ObservableValue 的实例化对象):

src/core/observable.ts

export function reportObserved(observable: IObservable) {
const derivation = globalState.trackingDerivation;
if (derivation !== null) {
if (derivation.runId !== observable.lastAccessedBy) {
observable.lastAccessedBy = derivation.runId;
derivation.newObserving![derivation.unboundDepsCount++] = observable;
}
} else if (observable.observers.length === 0) {
queueForUnobservation(observable);
}
}

在该方法中获取到了 globalState.trackingDerivation,他就是 autorun 初始化过程中的 Reaction 对象,然后将可观察属性对象(ObservableValue)添加到其 newObserving 数组中,以此来收集依赖;

之后将该 Reactionid 赋值给可观察属性对象的 lastAccessedBy 属性;

第二步

再回到 autorun 部分的 trackDerivedFunction 函数中,在执行完 view 函数之后,该函数依赖的可观察属性对象都已经收集到 derivation.newObserving 之中;

然后调用了 bindDependencies(derivation)函数:

src/core/derivation.ts

function bindDependencies(derivation: IDerivation) {
const prevObserving = derivation.observing;
const observing = derivation.observing = derivation.newObserving!;
let lowestNewObservingDerivationState = IDerivationState.UP_TO_DATE;

derivation.newObserving = null;

let i0 = 0, l = derivation.unboundDepsCount;
for (let i = 0; i < l; i++) {
const dep = observing[i];
if (dep.diffValue === 0) {
dep.diffValue = 1;
if (i0 !== i) observing[i0] = dep;
i0++;
}

if ((dep as any as IDerivation).dependenciesState > lowestNewObservingDerivationState) {
lowestNewObservingDerivationState = (dep as any as IDerivation).dependenciesState;
}
}
observing.length = i0;

l = prevObserving.length;
while (l--) {
const dep = prevObserving[l];
if (dep.diffValue === 0) {
removeObserver(dep, derivation);
}
dep.diffValue = 0;
}

while (i0--) {
const dep = observing[i0];
if (dep.diffValue === 1) {
dep.diffValue = 0;
addObserver(dep, derivation);
}
}

if (lowestNewObservingDerivationState !== IDerivationState.UP_TO_DATE) {
derivation.dependenciesState = lowestNewObservingDerivationState;
derivation.onBecomeStale();
}
}

export function addObserver(observable: IObservable, node: IDerivation) {
const l = observable.observers.length;
if (l) { // because object assignment is relatively expensive, let's not store data about index 0.
observable.observersIndexes[node.__mapid] = l;
}
observable.observers[l] = node;

if (observable.lowestObserverState > node.dependenciesState) observable.lowestObserverState = node.dependenciesState;
}

在该函数中,真正完成了对依赖的绑定:
首先 derivation.observing = derivation.newObserving,将该 Reaction 依赖的 ObservableValue 都存储在其 observing 属性中;
之后遍历每个 ObservableValue 通过 addObserver 方法将依赖 ObservableValueReaction 添加到 ObservableValueobservers 属性中;

因此,在 Reaction 中可以获取到其依赖的 ObservableValue ,而在 ObservableValue 中也可获取到依赖它的 Reaction;

这就完成了一个双向的绑定。

触发 Reaction

当目标对象中的可观察属性的值发生改变的时候,则会触发在传入 autorun 中依赖该属性的函数;
在这里主要是通过改变属性值时通过触发其属性描述中的 set 函数,在其中做出一系列响应。

先来观察一个可观察属性的 set 函数,它是在 generateObservablePropConfig 中所定义的:

src/types/observableobject.ts

export function generateObservablePropConfig(propName) {
return observablePropertyConfigs[propName] || (
observablePropertyConfigs[propName] = {
configurable: true,
enumerable: true,
get: function() {
return this.$mobx.values[propName].get();
},
set: function(v) {
setPropertyValue(this, propName, v);
}
}
);
}

在给该属性设置一个值的时候,触发了 setPropertyValue 函数:

src/types/observableobject.ts

export function setPropertyValue(instance, name: string, newValue) {
const adm = instance.$mobx;
const observable = adm.values[name];

if (hasInterceptors(adm)) {
const change = interceptChange<IObjectWillChange>(adm, {
type: "update",
object: instance,
name, newValue
});
if (!change)
return;
newValue = change.newValue;
}
newValue = observable.prepareNewValue(newValue);

if (newValue !== UNCHANGED) {
const notify = hasListeners(adm);
const notifySpy = isSpyEnabled();
const change = notify || notifySpy ? {
type: "update",
object: instance,
oldValue: (observable as any).value,
name, newValue
} : null;

if (notifySpy)
spyReportStart(change);
observable.setNewValue(newValue);
if (notify)
notifyListeners(adm, change);
if (notifySpy)
spyReportEnd();
}
}

setPropertyValue 函数中,先是获取到了目标对象的代理对象也就是 $mobx 属性,之后根据属性名从代理对象中的 values 获得到该属性对应的一个 ObservableValue 对象,之后判断新值和原值是否不等,然后执行 bservable.setNewValue(newValue) 方法;
ObservableValue.setNewValue 方法中:

src/types/observablevalue.ts

export class ObservableValue<T> extends BaseAtom implements IObservableValue<T>, IInterceptable<IValueWillChange<T>>, IListenable {
// ...

setNewValue(newValue: T) {
const oldValue = this.value;
this.value = newValue;
this.reportChanged();
if (hasListeners(this)) {
notifyListeners(this, {
type: "update",
object: this,
newValue, oldValue
});
}
}

// ...
}

调用了 this.reportChanged 方法,该方法在 ObservableValue 类中是从 BaseAtom 类继承而来:

export class BaseAtom implements IAtom {
isPendingUnobservation = true;
observers = [];
observersIndexes = {};

diffValue = 0;
lastAccessedBy = 0;
lowestObserverState = IDerivationState.NOT_TRACKING;
constructor(public name = "Atom@" + getNextId()) { }

public onBecomeUnobserved() {
// noop
}

public reportObserved() {
reportObserved(this);
}

public reportChanged() {
startBatch();
propagateChanged(this);
endBatch();
}

toString() {
return this.name;
}
}

BaseAtom.reportChanged 方法中调用 propagateChanged 函数,并将对象自身(ObservableValue)传入:

src/core/observable.ts

export function propagateChanged(observable: IObservable) {
if (observable.lowestObserverState === IDerivationState.STALE) return;
observable.lowestObserverState = IDerivationState.STALE;

const observers = observable.observers;
let i = observers.length;
while (i--) {
const d = observers[i];
if (d.dependenciesState === IDerivationState.UP_TO_DATE)
d.onBecomeStale();
d.dependenciesState = IDerivationState.STALE;
}
}

在该方法中,从传入的 ObservableValue 对象中获取到,依赖该可观察属性对象的 observer(Reaction)对象;
之后调用 ReactiononBecomeStale 方法;

再来回顾 Reaction 类:

src/core/reaction.ts

export class Reaction implements IDerivation, IReactionPublic {
observing: IObservable[] = []; // nodes we are looking at. Our value depends on these nodes
newObserving: IObservable[] = [];
dependenciesState = IDerivationState.NOT_TRACKING;
diffValue = 0;
runId = 0;
unboundDepsCount = 0;
__mapid = "#" + getNextId();
isDisposed = false;
_isScheduled = false;
_isTrackPending = false;
_isRunning = false;
errorHandler: (error: any, derivation: IDerivation) => void;

constructor(public name: string = "Reaction@" + getNextId(), private onInvalidate: () => void) { }

onBecomeStale() {
this.schedule();
}

schedule() {
if (!this._isScheduled) {
this._isScheduled = true;
globalState.pendingReactions.push(this);
runReactions();
}
}

isScheduled() {
return this._isScheduled;
}

runReaction() {
if (!this.isDisposed) {
startBatch();
this._isScheduled = false;
if (shouldCompute(this)) {
this._isTrackPending = true;

this.onInvalidate();
if (this._isTrackPending && isSpyEnabled()) {
// onInvalidate didn't trigger track right away..
spyReport({
object: this,
type: "scheduled-reaction"
});
}
}
endBatch();
}
}

track(fn: () => void) {
startBatch();
const notify = isSpyEnabled();
let startTime;
if (notify) {
startTime = Date.now();
spyReportStart({
object: this,
type: "reaction",
fn
});
}
this._isRunning = true;
const result = trackDerivedFunction(this, fn, undefined);
this._isRunning = false;
this._isTrackPending = false;
if (this.isDisposed) {
// disposed during last run. Clean up everything that was bound after the dispose call.
clearObserving(this);
}
if (isCaughtException(result))
this.reportExceptionInDerivation(result.cause);
if (notify) {
spyReportEnd({
time: Date.now() - startTime
});
}
endBatch();
}

// ...
}

执行 Reaction.onBecomeStale 方法中执行了 Reaction.schedule 方法,之后的流程和 autorun - 第二步 之后是一样的,会触发其监听函数,并更新依赖。

总结

总而言之,MobX 中实现数据绑定的原理主要是通过 Object.defineProperty ,在对状态进行读写操作的时候会触发其 gettersetter 函数以进行响应,而这一点和 Vue 的原理其实是非常类似的。

在初始化或者添加状态时会给目标对象添加一个代理对象,并且重写目标对象中需要观察的属性的 gettersetter 函数,讲其读写操作代理到代理对象之上;

当初始化 autorun 函数时,会首次运行一遍监听函数,以触发状态的 getter 函数,然后收集依赖,将依赖与监听函数进行双向绑定;

在之后修改状态时会触发其 setter 函数,获取 Reactions 并运行其监听函数,同时更新其监听函数的依赖;

因此,MobX 的依赖绑定是实时性的,并且实现了依赖的最小颗粒度。

留言

必填
必填,不公开
选填

正在加载验证码......