咱们在Mobx 源码解析 一(observable)已经知道了observable 作的事情了, 可是咱们的仍是没有讲解明白在咱们的Demo中,咱们在Button
的Click
事件中只是对bankUser.income
进行了自增和自减,并无对incomeLabel
进行操做, 可是incomeLabel
的内容却实时的更新了, 咱们分析只有在mobx.autorun
方法中对其的innerText
进行了处理, 因此很容易理解神秘之处在于此方法,接下来咱们来深刻分析这个方法的实现原理.node
在Git 上面建立了一个新的autorun分支, 对Demo 的代码进行小的变动,变动的主要是autorun 方法:react
const incomeDisposer = mobx.autorun(() => {
if (bankUser.income < 0) {
bankUser.income = 0
throw new Error('throw new error')
}
incomeLabel.innerText = `Ivan Fan income is ${bankUser.income}`
}, {
name: 'income',
delay: 2*1000,
onError: (e) => {
console.log(e)
}
})
复制代码
能够看出,咱们给autorun 方法传递了第二个参数, 并且是一个Object :git
{
name: 'income',
delay: 2*1000,
onError: (e) => {
console.log(e)
}
复制代码
咱们能够根据这三个属性能够猜想出:github
autorun
方法.autorun源码以下:数组
export function autorun(view, opts = EMPTY_OBJECT) {
if (process.env.NODE_ENV !== "production") {
invariant(typeof view === "function", "Autorun expects a function as first argument");
invariant(isAction(view) === false, "Autorun does not accept actions since actions are untrackable");
}
const name = (opts && opts.name) || view.name || "Autorun@" + getNextId();
const runSync = !opts.scheduler && !opts.delay;
let reaction;
if (runSync) {
// normal autorun
reaction = new Reaction(name, function () {
this.track(reactionRunner);
}, opts.onError);
}
else {
const scheduler = createSchedulerFromOptions(opts);
// debounced autorun
let isScheduled = false;
reaction = new Reaction(name, () => {
if (!isScheduled) {
isScheduled = true;
scheduler(() => {
isScheduled = false;
if (!reaction.isDisposed)
reaction.track(reactionRunner);
});
}
}, opts.onError);
}
function reactionRunner() {
view(reaction);
}
reaction.schedule();
return reaction.getDisposer();
}
复制代码
查看这个方法,发现其能够传递两个参数:bash
- view, 必须是一个function, 也就是咱们要执行的业务逻辑的地方.
- opts, 是一个可选参数, 并且是一个Object, 能够传递的属性有四个
name
,scheduler
,delay
,onError
, 其中delay和scheduler 是比较重要的两个参数,由于决定是否同步仍是异步.- 查看这个方法的最后第二行
reaction.schedule();
, 其实表示已经在autorun 方法调用时,会当即执行一次其对应的回调函数
在上面的梳理中发现, 若是传递了delay
或者scheduler
值,其进入的是else
逻辑分支,也就是异步处理分支,咱们如今先将demo 中的delay: 2*1000,
属性给注释, 先分析同步处理的逻辑( normal autorun 正常的autorun)异步
首先建立了一个 Reaction 是实例对象,其中传递了两个参数: name 和一函数, 这个函数挂载在一个叫onInvalidate
属性上,这个函数最终会执行咱们的autorun
方法的第一个参数viwe
, 也就是咱们要执行的业务逻辑代码:函数
reaction = new Reaction(name, function () {
this.track(reactionRunner);
}, opts.onError);
复制代码
function reactionRunner() {
view(reaction);
}
复制代码
咱们看到,实例化reaction
对象后,当即执行了其schedule
方法,而后就只是返回一个对象reaction.getDisposer()
对象, 整个autorun
方法就结束了。post
autorun
方法看起来很简单,可是为何能在其对应的属性变动时,就当即执行view
方法呢, 其奥妙应该在于schedule
方法中,因此咱们应该进一步分析这个方法.ui
schedule() {
if (!this._isScheduled) {
this._isScheduled = true;
globalState.pendingReactions.push(this);
runReactions();
}
}
复制代码
globalState.pendingReactions.push(this);
将当前实例放在一个全局的数组中globalState.pendingReactions
const MAX_REACTION_ITERATIONS = 100;
let reactionScheduler = f => f();
export function runReactions() {
if (globalState.inBatch > 0 || globalState.isRunningReactions)
return;
reactionScheduler(runReactionsHelper);
}
function runReactionsHelper() {
globalState.isRunningReactions = true;
const allReactions = globalState.pendingReactions;
let iterations = 0;
while (allReactions.length > 0) {
if (++iterations === MAX_REACTION_ITERATIONS) {
allReactions.splice(0); // clear reactions
}
let remainingReactions = allReactions.splice(0);
for (let i = 0, l = remainingReactions.length; i < l; i++)
remainingReactions[i].runReaction();
}
globalState.isRunningReactions = false;
}
复制代码
globalState.inBatch > 0 || globalState.isRunningReactions
是否有在运行的reaction.const allReactions = globalState.pendingReactions;
(咱们在schedule
方法分析中,在这个方法,将每个reaction 实例放到这个globalState 数组中)runReaction
方法( remainingReactions[i].runReaction();
)globalState.isRunningReactions = false;
这样就能够保证一次只有一个autorun
在运行,保证了数据的正确性咱们分析了基本流程,最终执行的是在Reaction 实例方法runReaction
方法中,咱们如今开始分析这个方法。
runReaction() {
if (!this.isDisposed) {
startBatch();
this._isScheduled = false;
if (shouldCompute(this)) {
this._isTrackPending = true;
try {
this.onInvalidate();
if (this._isTrackPending &&
isSpyEnabled() &&
process.env.NODE_ENV !== "production") {
spyReport({
name: this.name,
type: "scheduled-reaction"
});
}
}
catch (e) {
this.reportExceptionInDerivation(e);
}
}
endBatch();
}
}
复制代码
startBatch();
只是设置了globalState.inBatch++;
this.onInvalidate();
关键是这个方法, 这个方法是实例化Reaction 对象传递进来的,其最终代码以下:reaction = new Reaction(name, function () {
this.track(reactionRunner);
}, opts.onError);
复制代码
function reactionRunner() {
view(reaction);
}
复制代码
因此this.onInvalidate
其实就是:
function () {
this.track(reactionRunner);
}
复制代码
上面咱们已经分析了autorun 的基本运行逻辑, 咱们能够在this.track(reactionRunner);
地方,打个断点, 查看下function 的call stack.
- derivation,就是autorun 方法建立的Reaction 实例
- f, 就是autorun的回调函数, 也就是derivation的onInvalidate 属性
咱们查看到result = f.call(context);
,很明显这个地方是就是执行autorun方法回调函数的地方。
咱们看到在这个方法中将当前的derivation
赋值给了globalState.trackingDerivation = derivation;
,这个值在其余的地方会调用。 咱们再回过头来看下autorun 的回调函数究竟是个什么:
const incomeDisposer = autorun((reaction) => {
incomeLabel.innerText = `${bankUser.name} income is ${bankUser.income}`
})
复制代码
在这里,咱们调用了bankUser.name
, bankUser.income
,其中bankUser 是一个被observable 处理的对象,咱们在Mobx 源码解析 一(observable)中知道, 这个对象用Proxy 进行了代理, 咱们读取他的任何属性,都会键入拦截器的get 方法,咱们接下来分析下get 方法到底作了什么。
get 方法的代码以下:
get(target, name) {
if (name === $mobx || name === "constructor" || name === mobxDidRunLazyInitializersSymbol)
return target[name];
const adm = getAdm(target);
const observable = adm.values.get(name);
if (observable instanceof Atom) {
return observable.get();
}
if (typeof name === "string")
adm.has(name);
return target[name];
}
复制代码
在Mobx 源码解析 一(observable) 中咱们知道,observable 是一个ObservableValue 类型, 而ObservableValue 又继承与Atom, 因此代码会走以下分支:
if (observable instanceof Atom) {
return observable.get();
}
复制代码
咱们继续查看其对应的get 方法
get() {
this.reportObserved();
return this.dehanceValue(this.value);
}
复制代码
这里有一个关键的方法: this.reportObserved();, 顾名思义,就是我要报告我要被观察了,将observable 对象和autorun 方法给关联起来了,咱们能够继续跟进这个方法。
经过断点,咱们发现,最终会调用observable.js 的reportObserved方法。
export function reportObserved(observable) {
const derivation = globalState.trackingDerivation;
if (derivation !== null) {
if (derivation.runId !== observable.lastAccessedBy) {
observable.lastAccessedBy = derivation.runId;
derivation.newObserving[derivation.unboundDepsCount++] = observable;
if (!observable.isBeingObserved) {
observable.isBeingObserved = true;
observable.onBecomeObserved();
}
}
return true;
}
else if (observable.observers.size === 0 && globalState.inBatch > 0) {
queueForUnobservation(observable);
}
return false;
}
复制代码
const derivation = globalState.trackingDerivation;
这行代码和容易理解,就是从globalstate 取一个值,可是这个值的来源很重要, 上面咱们在derivation.js 的trackDerivedFunction 方法中,发现对其赋值了globalState.trackingDerivation = derivation;
。而其对应的值derivation
就是对应的autorun 建立的Reaction 对象derivation.newObserving[derivation.unboundDepsCount++] = observable;
这一行相当重要, 将observable对象的属性和autorun 方法真正关联了。在咱们的autorun 方法中调用了两个属性,因此在执行两次get 方法后,对应的globalState.trackingDerivation值以下图所示:
其中newObserving 属性中,有了两个值,着两个值,表示当前的这个autorun 方法,会监听这个两个属性,咱们接下来会解析,怎么去处理newObserving数组
咱们继续来分析trackDerivedFunction 方法
export function trackDerivedFunction(derivation, f, 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;
if (globalState.disableErrorBoundaries === true) {
result = f.call(context);
}
else {
try {
result = f.call(context);
}
catch (e) {
result = new CaughtException(e);
}
}
globalState.trackingDerivation = prevTracking;
bindDependencies(derivation);
return result;
}
复制代码
上面咱们已经分析完了result = f.call(context);
这一步骤, 咱们如今要分析: bindDependencies(derivation);方法
参数derivation ,在执行每一个属性的get 方法时, 已经给derivatio 的newObserving 属性添加了两条记录, 如图:
咱们接下来深刻分析bindDependencies 方法,发现其对newObserving 进行了遍历处理,以下
while (i0--) {
const dep = observing[i0];
if (dep.diffValue === 1) {
dep.diffValue = 0;
addObserver(dep, derivation);
}
}
复制代码
addObserver(dep, derivation);
,由方法名猜测,这个应该是去添加观察了,咱们查看下具体代码:
export function addObserver(observable, node) {
observable.observers.add(node);
if (observable.lowestObserverState > node.dependenciesState)
observable.lowestObserverState = node.dependenciesState;
}
复制代码
参数: observable 就是咱们每一个属性对应的ObservableValue, 有一个Set 类型的observers 属性 , node就是咱们autorun 方法建立的Reaction 对象
observable.observers.add(node); 就是每一个属性保存了其对应的观察者。
其最终将observable 的对象加工成以下图所示(给第三步的observes 添加了值):
咱们已经知道observable 对象和autorun 方法已经关联起来,咱们后续会继续分析,当改变observable 属性的值的时候,怎么去触发autorun 的回调函数。我如今的猜测是:首先确定会触发Proxy 的set方法,而后set方法会遍历调用observers 里面的Reaction 的onInvalidate 方法,只是猜测,咱们后面深刻分析下。