Mobx6核心源码解析(五): Reaction和事务

1,184 阅读4分钟

前言

前面的文章解析了“观察”部分的源码,既下面代码中的第一行。本文将介绍“反应”部分的代码,既下面代码中的第二行。

 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内部机制如下:

    1. reaction创建后,应该以runReaction或者通过调度(见autorun)启动
    1. onInvalidate应以某种方式调用this.track(someFunction)
    1. someFunction访问的所有可观察对象都将会被这个reaction观察到
    1. 一旦发生依赖改版,Reaction将会被再次调度(在变更或事务结束后)
    1. 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就是上文中的someFunctionview是传入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_

image.png

但是对于两个独立的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源码相当晦涩,系列可能很多地方表述不清,但只看本系列的第一篇,亦能见微知著。