Mobx——derivation 的依赖收集过程 (离职拷贝版

2021-05-20  本文已影响0人  zpkzpk

离职了,把 2019 年在公司写的文档 copy 出来。年头有点久,可能写的不太对,也不是很想改了~
注:本文档对应 mobx 版本为 4.15.4、mobx-vue 版本为 2.0.10

源码解析

1. autorun & reaction

先看一下 autonrun 和 reaction 的源码

function autorun(
    view: (r: IReactionPublic) => any,
    opts: IAutorunOptions = EMPTY_OBJECT
): IReactionDisposer {
    ...
    if (runSync) {
        reaction = new Reaction(
            name,
            function(this: Reaction) {
                this.track(reactionRunner)
            },
            opts.onError,
            opts.requiresObservable
        )
    } 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,
            opts.requiresObservable
        )
    }

    function reactionRunner() {
        view(reaction)
    }

    reaction.schedule()
    return reaction.getDisposer()
}


function reaction<T>(
    expression: (r: IReactionPublic) => T,
    effect: (arg: T, r: IReactionPublic) => void,
    opts: IReactionOptions = EMPTY_OBJECT
): IReactionDisposer {

    // 回调 effect 用 action 封装了一层
    const effectAction = action(
        name,
        opts.onError ? wrapErrorHandler(opts.onError, effect) : effect
    )

    const r = new Reaction(
        name,
        () => {
            if (firstTime || runSync) {
                reactionRunner()
            } else if (!isScheduled) {
                isScheduled = true
                scheduler!(reactionRunner)
            }
        },
        opts.onError,
        opts.requiresObservable
    )

    function reactionRunner() {
        isScheduled = false
        if (r.isDisposed) return
        let changed = false
        r.track(() => {
            const nextValue = expression(r)
            changed = firstTime || !equals(value, nextValue)
            value = nextValue
        })
        if (firstTime && opts.fireImmediately!) effectAction(value, r)
        if (!firstTime && (changed as boolean) === true) effectAction(value, r)
        if (firstTime) firstTime = false
    }

    r.schedule()
    return r.getDisposer()
}

其实就是 new 了一个 Reaction,只不过 autorun 把回调直接传进去 track 了,用起来比较无脑,而 reaction 则是把触发条件传进去 track,而回调则是用 action 包了一层,并且多一个 firstTime 参数来限制回调只能执行单次

这样带来的区别是什么?更加语义化的触发时机 + action 事务特性加持的回调函数

所以这里只需要搞懂 Reaction 就行了

2. Reaction

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
    isTracing: TraceMode = TraceMode.NONE

    constructor(
        public name: string = "Reaction@" + getNextId(),
        private onInvalidate: () => void, 
        private errorHandler?: (error: any, derivation: IDerivation) => void,
        public requiresObservable = false
    ) {}

    onBecomeStale() {
        this.schedule()
    }

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

    isScheduled() {
        return this._isScheduled
    }

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

                try {
                    this.onInvalidate() // 调用入参回调函数,也就是this.track(incomeFunction)
                    if (
                        this._isTrackPending &&
                        isSpyEnabled() &&
                        process.env.NODE_ENV !== "production"
                    ) {
                        // onInvalidate didn't trigger track right away..
                        spyReport({
                            name: this.name,
                            type: "scheduled-reaction"
                        })
                    }
                } catch (e) {
                    this.reportExceptionInDerivation(e)
                }
            }
            endBatch()
        }
    }

    track(fn: () => void) {
        startBatch()
        const notify = isSpyEnabled()
        let startTime
        if (notify) {
            startTime = Date.now()
            spyReportStart({
                name: this.name,
                type: "reaction"
            })
        }
        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 && process.env.NODE_ENV !== "production") {
            spyReportEnd({
                time: Date.now() - startTime
            })
        }
        endBatch()
    }
    ...
}

然后追一下 reaction(expression, effect, opts) 里的调用顺序

  1. 利用 action 和 第二个入参 effect (待执行的回调函数)定义 effectAction
  2. const r = new Reaction
  3. r.schedule 也就是 Reaction.schedule
  4. Reaction.runReaction
  5. this.onInvalidate, 这里 onInvalidate 是 new Reaction 里 第二个参数,传进来的是 r.track()
  6. track 里面的核心内容是trackDerivedFunction,具体源码如下:

3. trackDerivedFunction

trackDerivedFunction(this, fn, undefined) 这里 fn 就是 reaction 里 第一个入参(依赖相关的条件函数) expression 对应的操作:

const nextValue = expression(r)
changed = firstTime || !equals(value, nextValue)
value = nextValue

// 追下 trackDerivedFunction 都干了啥
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

    // 用 Vue 的思路来理解这句话的意义大概就是当前的 Watcher 是这个 Reaction,然后触发的 Dep 会塞到这个 Watcher 里,这里是个暂时借用,get完之后又还回去了
    globalState.trackingDerivation = derivation
    let result

    // 这里回调f被执行掉了,触发变量的 get、获取 dep
    if (globalState.disableErrorBoundaries === true) {
        result = f.call(context)
    } else {
        try {
            result = f.call(context)
        } catch (e) {
            result = new CaughtException(e)
        }
    }
    globalState.trackingDerivation = prevTracking

    // 建立 dep 与 derivation 的依赖关系
    bindDependencies(derivation)

    warnAboutDerivationWithoutDependencies(derivation)
    allowStateReadsEnd(prevAllowStateReads)
    return result
}

4. bindDependecies

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

    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

    derivation.newObserving = null

    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
            // 这个就是把 dep 和 Reaction 建立联系,observable.observers.add(node),这个 node 是 Reaction 就好像把 Watcher 绑到 dep 的 target 上
            addObserver(dep, derivation)
        }
    }

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

看到这里 derivation.observing 也就有东西了,增加了对应的 dep 依赖关系。

mobx 的依赖关系是这样的,拿 Reaction 举例 Reaction.observing 下面是对应的 dep,这层关系就是我该被谁的变动唤醒,dep 下面的 observers 就是对应的 derivation: IDerivation(Reaction 或 ComputedValue),这层关系就是,我变动了应该去通知谁做后续的响应。对应 vue 就是 Watcher 的 Deps 里面是 dep、dep 的 target 和 subs 里面是 Watcher

结论

逻辑上真的和 Vue 神似,但是给我的直观感受就是(其实我对语义化的反应很迟钝):

因为主要接触的 vue 是 js 的 vue,里面提供一大类功能的东西没有这么细的拆分,比如 IListenable 接口对应一堆类,用一个 demo 一点一点跟的时候,思路单一,感觉不到很庞大,但是有的时候,比如 observers 在我的 demo 里就对应 Reaction ,但是他也可能对应 ComputedValue ,二者之间的逻辑并不是完全一致的,都有各自的操作,虽然 vue 里面 computed 创建的 Watcher 和其他 Watcher 派发更新时的逻辑也完全不同,但是他们都是 Watcher,虽然读起来没有区别,只不过是初始化参数的差异,但是意义十分明确,就是我是 一个Watcher。

反观 ComputedValue:

class ComputedValue<T> implements IObservable, IComputedValue<T>, IDerivation

因为在派发更新的触发时 ComputedValue 和 ObservableValue 并列(这层关系没看懂有什么意义,因为 computedValue 一般都是只读的?)

所以把它塞在 ObservableObjectAdministration 的 value 下面,什么时候才能出发 set 操作?

但是在依赖收集的过程和派发更新执行时,他又和 Reaction 并列,从语义上直接读,只会感觉他和 ObservableValue 有关系,却读不出来 Reaction 和 ComputedValue 都是 IDerivation,就会感觉很蒙。

上一篇下一篇

猜你喜欢

热点阅读