Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mobx依赖收集和依赖更新原理浅析 #20

Open
mbaxszy7 opened this issue Jul 4, 2020 · 0 comments
Open

mobx依赖收集和依赖更新原理浅析 #20

mbaxszy7 opened this issue Jul 4, 2020 · 0 comments
Labels

Comments

@mbaxszy7
Copy link
Owner

mbaxszy7 commented Jul 4, 2020

先看下面的代码:

class Demo {
  @observable
  public test = 1

  log: () => void = autorun(() => {
    console.log(`test input onChange: ${this.test}`)
  })
}

修改test的值,会触发log函数自动执行。相当于传入autorun的方法,会自动收集依赖到的 observable值的变化。个人猜测autorun函数的工作方式是这样的

function autorun (fn) { 
   // 依赖收集的准备工作
   fn() // 触发observable属性的get方法
   // 清理依赖收集
}

下面来简单看一下autorun源码 (5.15.4)

function autorun(
    view: (r: IReactionPublic) => any,
    opts: IAutorunOptions = EMPTY_OBJECT
): IReactionDisposer {
 
    const name: string = (opts && opts.name) || (view as any).name || "Autorun@" + getNextId()
    const runSync = !opts.scheduler && !opts.delay
    let reaction: Reaction
    
    // 只看同步的autorun,异步是根据传入的delay setTimeout
    if (runSync) {
        // normal autorun
        reaction = new Reaction(
            name,
            // reaction的onInvalidate, 用track调用reactionRunner, 也就是view(reaction), (重新)收集依赖
            function(this: Reaction) {
                this.track(reactionRunner)
            },
            opts.onError,
            opts.requiresObservable
        )
    } else {
       // ... 处理异步
    }

    function reactionRunner() {
        view(reaction)
    }
    // 将 reaction 放进全局 globalState.pendingReactions 队列,里面会执行runReactions
    reaction.schedule()
    // 返回取消订阅
    return reaction.getDisposer()
}

再来看看runReactions,runReactions是依赖收集启动方法

let reactionScheduler: (fn: () => void) => void = f => f();

function runReactions() {
  // 不在事务中并且没有正在执行的reaction
  if (globalState.inBatch > 0 || globalState.isRunningReactions) return
  // 核心的调用runReactionsHelper
  reactionScheduler(runReactionsHelper)
}

runReactionsHelper:

function runReactionsHelper() {
    globalState.isRunningReactions = true
    const allReactions = globalState.pendingReactions
    let iterations = 0

    // 遍历所有globalState.pendingReactions中的reaction,并执行每个对象的runReaction
    while (allReactions.length > 0) {
        if (++iterations === MAX_REACTION_ITERATIONS) {
            console.error(
                `Reaction doesn't converge to a stable state after ${MAX_REACTION_ITERATIONS} iterations.` +
                    ` Probably there is a cycle in the reactive function: ${allReactions[0]}`
            )
            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
}

runReaction关键就是触发onInvalidate参数函数, 也就是用track包裹的view函数(autorun的传入函数)

    // fn 就是view 函数
    track(fn: () => void) {
   
        startBatch()
        ....
        this._isRunning = true
        // trackDerivedFunction是核心, 把fn传入了trackDerivedFunction,
        const result = trackDerivedFunction(this, fn, undefined)
        this._isRunning = false
        ....
        endBatch()
    }

终于到trackDerivedFunction了,trackDerivedFunction就是最终调用autorun的传入函数的方法。至此就完成了触发observable属性的get方法。后面就是监听observable属性的get方法的调用, 最终完成依赖收集。例如在mobx ObservableValue类中有一个get方法,这个方法就是trap了observable属性的get方法:

  public get(): T {
        // reportObserved将 observable 上报给正在收集依赖的 derivation (reaction)
        //  derivation 从globalState.trackingDerivation中获取,globalState.trackingDerivation在上面提到的最终触发autorun的传入 
       //    函数 的 trackDerivedFunction中设置的
        this.reportObserved()
        return this.dehanceValue(this.value)
    }

至此完成了依赖收集。
下面来简单的说一下obserable属性的set方法,触发set方法,如果值改变,mobx会通知此obserable属性的依赖:

    public set(newValue: T) {
        const oldValue = this.value
        newValue = this.prepareNewValue(newValue) as any
        if (newValue !== globalState.UNCHANGED) {
             ...
            //值改变, 触发setNewValue, 最终会触发Reaction 中的onBecomeStale, 而onBecomeStale调用的就是 this.schedule(),这个就合前面的依赖收集重合了
            this.setNewValue(newValue)
            if (notifySpy && process.env.NODE_ENV !== "production") spyReportEnd()
        }
    }

最后,我们也可以看到mobx抽离Reaction这一层,设计的很巧妙,不仅抽离了依赖收集的逻辑,管理全局的依赖管理,也抽离了不同依赖管理阶段side effect,在初始化依赖收集的时候可以设置track get 方法,在依赖更新的时候也可以将Reaction用作他处,比如React,进行组件的更新。

@mbaxszy7 mbaxszy7 changed the title mobx依赖收集原理浅析 mobx依赖收集和依赖更新原理浅析 Jul 4, 2020
@mbaxszy7 mbaxszy7 added the mobx label Jul 4, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant