Mobx 源码解析 二(autorun)

栏目: JavaScript · 发布时间: 8年前

内容简介:我们在在Git 上面创建了一个新的可以看出,我们给autorun 方法传递了第二个参数, 而且是一个Object :

我们在 Mobx 源码解析 一(observable) 已经知道了 observable 做的事情了, 但是我们的还是没有讲解明白在我们的 Demo 中,我们在 Button 的 Click 事件中只是对 bankUser.income 进行了自增和自减,并没有对 incomeLabel 进行操作, 但是 incomeLabel 的内容却实时的更新了, 我们分析只有在 mobx.autorun 方法中对其的 innerText 进行了处理, 所以很容易理解神秘之处在于此方法,接下来我们来深入分析这个方法的实现原理.

Demo

在Git 上面创建了一个新的 autorun 分支, 对Demo 的代码进行小的变更,变更的主要是autorun 方法:

const incomeDisposer = mobx.autorun(() => {
    if (bankUser.income < 0) {
        bankUser.income = 0
        throw new Error('throw new error')
    } 
    incomeLabel.innerText = `Ivan Fan income is ${bankUser.income}`   
}, {
    name: 'income',
    delay: 2*1000,
    onError: (e) => {
        console.log(e)
    }
})
复制代码

可以看出,我们给autorun 方法传递了第二个参数, 而且是一个Object :

{
    name: 'income',
    delay: 2*1000,
    onError: (e) => {
        console.log(e)
    }
复制代码

我们可以根据这三个属性可以猜测出:

autorun

autorun

autorun源码如下:

export function autorun(view, opts = EMPTY_OBJECT) {
    if (process.env.NODE_ENV !== "production") {
        invariant(typeof view === "function", "Autorun expects a function as first argument");
        invariant(isAction(view) === false, "Autorun does not accept actions since actions are untrackable");
    }
    const name = (opts && opts.name) || view.name || "Autorun@" + getNextId();
    const runSync = !opts.scheduler && !opts.delay;
    let reaction;
    if (runSync) {
        // normal autorun
        reaction = new Reaction(name, function () {
            this.track(reactionRunner);
        }, opts.onError);
    }
    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);
    }
    function reactionRunner() {
        view(reaction);
    }
    reaction.schedule();
    return reaction.getDisposer();
}
复制代码

查看这个方法,发现其可以传递两个参数:

  1. view, 必须是一个function, 也就是我们要执行的业务逻辑的地方.
  1. opts, 是一个可选参数, 而且是一个Object, 可以传递的属性有四个 name , scheduler , delay , onError , 其中delay和scheduler 是比较重要的两个参数,因为决定是否同步还是异步.
  2. 查看这个方法的最后第二行 reaction.schedule(); , 其实表示已经在autorun 方法调用时,会立即执行一次其对应的回调函数

同步处理

在上面的梳理中发现, 如果传递了 delay 或者 scheduler 值,其进入的是 else 逻辑分支,也就是异步处理分支,我们现在先将 demo 中的 delay: 2*1000, 属性给注释, 先分析同步处理的逻辑( normal autorun 正常的autorun)

创建reaction(反应)实例

首先创建了一个Reaction是实例对象,其中传递了两个参数: name 和一函数, 这个函数挂载在一个叫 onInvalidate 属性上,这个函数最终会执行我们的 autorun 方法的第一个参数 viwe , 也就是我们要执行的业务逻辑代码:

reaction = new Reaction(name, function () {
            this.track(reactionRunner);
        }, opts.onError);
复制代码
function reactionRunner() {
        view(reaction);
    }
复制代码

调用reaction.schedule()方法

我们看到,实例化 reaction 对象后,立即执行了其 schedule 方法,然后就只是返回一个对象 reaction.getDisposer() 对象, 整个 autorun 方法就结束了。

autorun 方法看起来很简单,但是为什么能在其对应的属性变更时,就立即执行 view 方法呢, 其奥妙应该在于 schedule 方法中,所以我们应该进一步分析这个方法.

schedule() {
        if (!this._isScheduled) {
            this._isScheduled = true;
            globalState.pendingReactions.push(this);
            runReactions();
        }
    }
复制代码
  1. 设置一个标识:_isScheduled = true, 表示当前实例已经在安排中
  2. globalState.pendingReactions.push(this); 将当前实例放在一个全局的数组中 globalState.pendingReactions
  3. 运行runReactions 方法.

runReactions 方法(运行所有的reaction)

const MAX_REACTION_ITERATIONS = 100;
let reactionScheduler = f => f();
export function runReactions() {
    if (globalState.inBatch > 0 || globalState.isRunningReactions)
        return;
    reactionScheduler(runReactionsHelper);
}
function runReactionsHelper() {
    globalState.isRunningReactions = true;
    const allReactions = globalState.pendingReactions;
    let iterations = 0;   
    while (allReactions.length > 0) {
        if (++iterations === MAX_REACTION_ITERATIONS) {
            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;
}
复制代码
  1. 判断全局变量 globalState.inBatch > 0 || globalState.isRunningReactions 是否有在运行的reaction.
  2. 运行runReactionsHelper() 方法
  3. 设置 globalState.isRunningReactions = true;
  4. 获取所有等待中的reaction, const allReactions = globalState.pendingReactions; (我们在 schedule 方法分析中,在这个方法,将每一个reaction 实例放到这个globalState 数组中)
  5. 遍历所有等待中的reaction 然后去运行 runReaction 方法( remainingReactions[i].runReaction(); )
  6. 最后将 globalState.isRunningReactions = false; 这样就可以保证一次只有一个 autorun 在运行,保证了数据的正确性

我们分析了基本流程,最终执行的是在 Reaction 实例方法 runReaction 方法中,我们现在开始分析这个方法。

runReaction 方法(真正执行autorun 中的业务逻辑)

runReaction() {
        if (!this.isDisposed) {
            startBatch();
            this._isScheduled = false;
            if (shouldCompute(this)) {
                this._isTrackPending = true;
                try {
                    this.onInvalidate();
                    if (this._isTrackPending &&
                        isSpyEnabled() &&
                        process.env.NODE_ENV !== "production") {
                        spyReport({
                            name: this.name,
                            type: "scheduled-reaction"
                        });
                    }
                }
                catch (e) {
                    this.reportExceptionInDerivation(e);
                }
            }
            endBatch();
        }
    }
复制代码
  1. startBatch(); 只是设置了 globalState.inBatch++;
  2. this.onInvalidate(); 关键是这个方法, 这个方法是实例化 Reaction 对象传递进来的,其最终代码如下:
reaction = new Reaction(name, function () {
        this.track(reactionRunner);
    }, opts.onError);
复制代码
function reactionRunner() {
        view(reaction);
    }
复制代码

所以 this.onInvalidate 其实就是:

function () {
     this.track(reactionRunner);
}
复制代码

如何和observable 处理过的对象关联?

上面我们已经分析了autorun 的基本运行逻辑, 我们可以在 this.track(reactionRunner); 地方,打个断点, 查看下function 的call stack.

Mobx 源码解析 二(autorun)
最终回调 derivation.js

的trackDerivedFunction 方法, 这个方法有三个参数:

  1. derivation,就是autorun 方法创建的 Reaction 实例
  1. f, 就是autorun的回调函数, 也就是derivation的onInvalidate 属性

我们查看到 result = f.call(context); ,很明显这个地方是就是执行autorun方法回调函数的地方。

我们看到在这个方法中将当前的 derivation 赋值给了 globalState.trackingDerivation = derivation; ,这个值在其他的地方会调用。 我们再回过头来看下 autorun 的回调函数到底是个什么:

const incomeDisposer = autorun((reaction) => {
    incomeLabel.innerText = `${bankUser.name} income is ${bankUser.income}`
})
复制代码

在这里,我们调用了 bankUser.name , bankUser.income ,其中 bankUser 是一个被 observable 处理的对象,我们在 Mobx 源码解析 一(observable) 中知道, 这个对象用 Proxy 进行了代理, 我们读取他的任何属性,都会键入拦截器的 get 方法,我们接下来分析下 get 方法到底做了什么。

Proxy get 方法

get 方法的代码如下:

get(target, name) {
        if (name === $mobx || name === "constructor" || name === mobxDidRunLazyInitializersSymbol)
            return target[name];
        const adm = getAdm(target);
        const observable = adm.values.get(name);
        if (observable instanceof Atom) {
            return observable.get();
        }
        if (typeof name === "string")
            adm.has(name);
        return target[name];
    }
复制代码

在 Mobx 源码解析 一(observable) 中我们知道,observable 是一个ObservableValue 类型, 而ObservableValue 又继承与Atom, 所以代码会走如下分支:

if (observable instanceof Atom) {
            return observable.get();
        }
复制代码

我们继续查看其对应的get 方法

get() {
        this.reportObserved();
        return this.dehanceValue(this.value);
    }
复制代码

这里有一个关键的方法:this.reportObserved();, 顾名思义,就是我要报告我要被观察了,将 observable 对象和 autorun 方法给关联起来了,我们可以继续跟进这个方法。

通过断点,我们发现,最终会调用 observable.js 的reportObserved方法。

Mobx 源码解析 二(autorun)

其方法的具体代码如下,我们会一行行的进行分析

export function reportObserved(observable) {
    const derivation = globalState.trackingDerivation;
    if (derivation !== null) {
        if (derivation.runId !== observable.lastAccessedBy) {
            observable.lastAccessedBy = derivation.runId;
            derivation.newObserving[derivation.unboundDepsCount++] = observable;
            if (!observable.isBeingObserved) {
                observable.isBeingObserved = true;
                observable.onBecomeObserved();
            }
        }
        return true;
    }
    else if (observable.observers.size === 0 && globalState.inBatch > 0) {
        queueForUnobservation(observable);
    }
    return false;
}
复制代码
  1. 参数:observable 是一个 ObservableValue 对象, 在第一章节的分析,我们已经知道经过observable 加工过的对象,每个属性被加工这个类型的对象,所以这个对象,也就是对应的属性。
  2. 第二行 const derivation = globalState.trackingDerivation; 这行代码和容易理解,就是从globalstate 取一个值,但是这个值的来源很重要, 上面我们在 derivation.js 的trackDerivedFunction 方法中,发现对其赋值了 globalState.trackingDerivation = derivation; 。而其对应的值 derivation 就是对应的 autorun 创建的 Reaction 对象
  3. derivation.newObserving[derivation.unboundDepsCount++] = observable; 这一行至关重要, 将observable对象的属性和autorun 方法真正关联了。

在我们的 autorun 方法中调用了两个属性,所以在执行两次 get 方法后,对应的 globalState.trackingDerivation 值如下图所示:

Mobx 源码解析 二(autorun)

其中 newObserving 属性中,有了两个值,着两个值,表示当前的这个autorun 方法,会监听这个两个属性,我们接下来会解析,怎么去处理 newObserving 数组

我们继续来分析trackDerivedFunction 方法

export function trackDerivedFunction(derivation, f, context) {
    changeDependenciesStateTo0(derivation);
    derivation.newObserving = new Array(derivation.observing.length + 100);
    derivation.unboundDepsCount = 0;
    derivation.runId = ++globalState.runId;
    const prevTracking = globalState.trackingDerivation;
    globalState.trackingDerivation = derivation;
    let result;
    if (globalState.disableErrorBoundaries === true) {
        result = f.call(context);
    }
    else {
        try {
            result = f.call(context);
        }
        catch (e) {
            result = new CaughtException(e);
        }
    }
    globalState.trackingDerivation = prevTracking;
    bindDependencies(derivation);
    return result;
}
复制代码

上面我们已经分析完了 result = f.call(context); 这一步骤, 我们现在要分析: bindDependencies(derivation);方法

bindDependencies 方法

参数 derivation ,在执行每个属性的 get 方法时, 已经给 derivatio 的 newObserving 属性添加了两条记录, 如图:

Mobx 源码解析 二(autorun)

我们接下来深入分析 bindDependencies 方法,发现其对 newObserving 进行了遍历处理,如下

while (i0--) {
        const dep = observing[i0];
        if (dep.diffValue === 1) {
            dep.diffValue = 0;
            addObserver(dep, derivation);
        }
    }
复制代码

addObserver(dep, derivation); ,由方法名猜想,这个应该是去添加观察了,我们查看下具体代码:

export function addObserver(observable, node) {
    observable.observers.add(node);
    if (observable.lowestObserverState > node.dependenciesState)
        observable.lowestObserverState = node.dependenciesState;
}
复制代码

参数: observable 就是我们每个属性对应的 ObservableValue , 有一个 Set 类型的observers 属性 , node就是我们autorun 方法创建的 Reaction 对象

observable.observers.add(node); 就是每个属性保存了其对应的观察者。

其最终将 observable 的对象加工成如下图所示(给第三步的observes 添加了值):

Mobx 源码解析 二(autorun)

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。

Effective C# 中文版

Effective C# 中文版

Bill Wagner / 李建忠 / 人民邮电出版社 / 2007-5 / 49.00元

本书围绕一些关于C#和.NET的重要主题,包括C#语言元素、.NET资源管理、使用C#表达设计、创建二进制组件和使用框架等,讲述了最常见的50个问题的解决方案,为程序员提供了改善C#和.NET程序的方法。本书通过将每个条款构建在之前的条款之上,并合理地利用之前的条款,来让读者最大限度地学习书中的内容,为其在不同情况下使用最佳构造提供指导。 本书适合各层次的C#程序员阅读,同时可以推荐给高校教......一起来看看 《Effective C# 中文版》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具