前言
前面的文章解析了“观察”部分的源码,既下面代码中的第一行。本文将介绍“反应”部分的代码,既下面代码中的第二行。
const target = observable(obj)
autorun(() => { console.log('target.value=', target.value) })
autorun
在介绍Reaction之前先介绍一下autorun:
export function autorun(
view: (r: IReactionPublic) => any,
opts: IAutorunOptions = EMPTY_OBJECT
): IReactionDisposer {
...
// normal autorun
reaction = new Reaction(
name,
function (this: Reaction) {
this.track(reactionRunner)
},
opts.onError,
opts.requiresObservable
)
...
function reactionRunner() {
view(reaction)
}
reaction.schedule_()
return reaction.getDisposer_()
}
autorun中最重要的事情就是创建了一个Reaction实例,并执行了这个实例的schedule_方法,驱动反应运行的,正式这个方法。
Reaction
Reaction是一种特殊的derivation,derivation都有一个状态,代表它的依赖是否更新:
export enum IDerivationState_ {
// 没有依赖
NOT_TRACKING_ = -1,
// 没有更新
UP_TO_DATE_ = 0,
// 可能更新(依赖的某个深度属性变更,但依赖本身引用未可能未更新,此时可以按需处理)
POSSIBLY_STALE_ = 1,
// 更新
STALE_ = 2
}
Reaction内部机制如下:
-
- reaction创建后,应该以
runReaction或者通过调度(见autorun)启动
- reaction创建后,应该以
-
onInvalidate应以某种方式调用this.track(someFunction)
-
someFunction访问的所有可观察对象都将会被这个reaction观察到
-
- 一旦发生依赖改版,Reaction将会被再次调度(在变更或事务结束后)
-
onInvalidate调用后会返回第一步 我们还是来看它的源码:
export class Reaction implements IDerivation, IReactionPublic {
...
}
constructor
constructor(
public name_: string = __DEV__ ? "Reaction@" + getNextId() : "Reaction",
private onInvalidate_: () => void,
private errorHandler_?: (error: any, derivation: IDerivation) => void,
public requiresObservable_ = false
) {}
这里的onInvalidate_参数,是一个调用了它的track方法的函数
function (this: Reaction) {
this.track(reactionRunner)
},
reactionRunner就是上文中的someFunction,view是传入autorun的函数。
function reactionRunner() {
view(reaction)
}
schedule_
schedule_() {
if (!this.isScheduled_) {
this.isScheduled_ = true
globalState.pendingReactions.push(this)
runReactions()
}
}
刚创建时, this.isScheduled_为false,这个reaction会被放入globalState.pendingReactions数组中,并开始运行:runReactions会从globalState.pendingReactions取出所有的reaction,并依次执行它的runReaction_方法。
runReaction_
runReaction_() {
if (!this.isDisposed_) {
startBatch()
this.isScheduled_ = false
const prev = globalState.trackingContext
globalState.trackingContext = this
if (shouldCompute(this)) {
this.isTrackPending_ = true
try {
this.onInvalidate_()
} catch (e) {
this.reportExceptionInDerivation_(e)
}
}
globalState.trackingContext = prev
endBatch()
}
}
reaction被创建时,都是IDerivationState_.NOT_TRACKING_的,因此会执行this.onInvalidate_(),也就是调用了这个函数:
function (this: Reaction) {
this.track(reactionRunner)
},
track
track(fn: () => void) {
...
startBatch()
let startTime
this.isRunning_ = true
const prevReaction = globalState.trackingContext // reactions could create reactions...
globalState.trackingContext = this
const result = trackDerivedFunction(this, fn, undefined)
globalState.trackingContext = prevReaction
this.isRunning_ = false
this.isTrackPending_ = false
if (isCaughtException(result)) this.reportExceptionInDerivation_(result.cause)
endBatch()
}
这里有一个关键调用:trackDerivedFunction
- trackDerivedFunction
这个函数接收了这个reaction和
someFunction:
export function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context: any) {
const prevAllowStateReads = allowStateReadsStart(true)
changeDependenciesStateTo0(derivation)
derivation.newObserving_ = new Array(derivation.observing_.length + 100)
derivation.unboundDepsCount_ = 0
derivation.runId_ = ++globalState.runId
const prevTracking = globalState.trackingDerivation
globalState.trackingDerivation = derivation
globalState.inBatch++
let result
...
result = f.call(context)
globalState.inBatch--
globalState.trackingDerivation = prevTracking
bindDependencies(derivation)
warnAboutDerivationWithoutDependencies(derivation)
allowStateReadsEnd(prevAllowStateReads)
return result
}
这里看到f.call(context),意味着我们传入autorun的函数被执行了。
另外,这里的globalState.trackingDerivation = derivation呼应了reportObserved函数中的const derivation = globalState.trackingDerivation。
同样,这里也有一个关键调用bindDependencies:
- bindDependencies
function bindDependencies(derivation: IDerivation) {
...
const prevObserving = derivation.observing_
const observing = (derivation.observing_ = derivation.newObserving_!)
let lowestNewObservingDerivationState = IDerivationState_.UP_TO_DATE_
...
observing.length = i0
while (i0--) {
const dep = observing[i0]
if (dep.diffValue_ === 1) {
dep.diffValue_ = 0
addObserver(dep, derivation)
}
}
}
从derivation.newObserving_取出observable,并通过addObserver,将这个reaction加到observable的observers_属性中。这一点也与前文中propagateChanged函数的逻辑对应
到这里,上一篇文章中遗留的两个问题都得到了回答。
onBecomeStale_
在propagateChanged中会执行每个reaction的onBecomeStale_方法:
onBecomeStale_() {
this.schedule_()
}
它会调动前文中的schedule_,于是runReaction_、onInvalidate_、track,又开启新一轮调用。
事务
Mobx中还有一个重要的特性:事务。
startBatch与endBatch
Mobx维护了一个全局变量globalState.inBatch,startBatch时加1,endBatch时减1,当为0,当为1时(--globalState.inBatch === 0),会调用runReactions。
每一次变更开始都会调用startBatch,结束时调用endBatch。过程中可能多次调用runReactions,但每个reaction只会调用一次runReaction_。
但是对于两个独立的setter所触发的反应,并不会合并到同一个事务里。这也是为什么Mobx建议所有的变更都放在action中。我们看runInAction的源码:
export function runInAction<T>(fn: () => T): T {
return executeAction(fn.name || DEFAULT_ACTION_NAME, false, fn, this, undefined)
}
export function executeAction(
actionName: string,
canRunAsDerivation: boolean,
fn: Function,
scope?: any,
args?: IArguments
) {
const runInfo = _startAction(actionName, canRunAsDerivation, scope, args)
try {
return fn.apply(scope, args)
} catch (err) {
runInfo.error_ = err
throw err
} finally {
_endAction(runInfo)
}
}
其中_startAction和_endAction分别调用了一次startBatch()和 endBatch(),这使得多次变更触发的runReactions始终因为globalState.inBatch>0挂起,直到最后一次endBatch()将globalState.inBatch置为0后再让各reaction执行runReaction_()
总结
本文分析了Reaction的源码和执行过程,以及Mobx事务的原理。结合前面几篇,Mobx底层运行逻辑已经全部讲完。当然Mobx的源码还有很多未尽之处,但万变不离其宗。由于Mobx源码相当晦涩,系列可能很多地方表述不清,但只看本系列的第一篇,亦能见微知著。