美文网首页
Mobx——derivation 的依赖收集过程 (离职拷贝版

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

作者: zpkzpk | 来源:发表于2021-05-20 10:39 被阅读0次

    离职了,把 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,就会感觉很蒙。

    相关文章

      网友评论

          本文标题:Mobx——derivation 的依赖收集过程 (离职拷贝版

          本文链接:https://www.haomeiwen.com/subject/qbsxzktx.html