博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Mobx 源码解析 二(autorun)
阅读量:6213 次
发布时间:2019-06-21

本文共 10939 字,大约阅读时间需要 36 分钟。

前言

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

Demo

在Git 上面创建了一个新的分支, 对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)    }复制代码

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

  1. name 应该是对这个一个简单的命名
  2. delay 应该是延迟执行
  3. onError 应该是在autorun 方法执行报错的时候执行的 以上只是根据代码猜测,我们接下来根据源码来具体分析这个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.

最终回调
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 处理的对象,我们在中知道, 这个对象用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];    }复制代码

在 中我们知道,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方法。

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

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值如下图所示:

其中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 方法时, 已经给derivationewObserving 属性添加了两条记录, 如图:

我们接下来深入分析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 添加了值):

总结

  1. 运行autorun 方法,会产生一个Reaction 类型的对象
  2. 运行autorun 方法的回调函数(参数),在这个函数里面会引用我们 observable 对象的一些属性,然后就会触发对应的Proxy Get 方法
  3. 在get 方法里, 会将对应的属性装饰过的ObservableValue 对象保存到第一点中的Reaction 对象 的newObserving数组中(如果在autorun回调函数中,有引用两个observable 属性, 则 newObserving会有两条记录)
  4. 运行完回调函数后,会去调用一个bindDependencies 方法, 回去遍历newObserving数组,将第一点中生成的Reaction 对象,保存到每个属性对应的ObservableValue 对象的 observers属性中,如果一个属性被多个autorun方法引用, 则observers属性会保存所有的Reaction 的对象(其实相当于观察者模式中的所有的监听者)
  5. 最终将observable 对象加工成了如下图的对象
  6. 所以其实autorun 函数,是给上图中的第三点中的observers 添加了值,也就是监听者。

Todo

我们已经知道observable 对象和autorun 方法已经关联起来,我们后续会继续分析,当改变observable 属性的值的时候,怎么去触发autorun 的回调函数。我现在的猜想是:首先肯定会触发Proxy 的set方法,然后set方法会遍历调用observers 里面的ReactiononInvalidate 方法,只是猜想,我们后面深入分析下。

转载地址:http://ebpja.baihongyu.com/

你可能感兴趣的文章
PPoE 与PPP 协议分析
查看>>
打包war 提示xxx v2不存在解决方法
查看>>
都知道的spring事务那点事(声明式,编程式)
查看>>
商业广告《手》的动画渲染制作
查看>>
Highcharts图表结构分析:详解标题与副标题
查看>>
strongswan ikev2 server on ubuntu 14.04
查看>>
ffmpeg切片命令
查看>>
atom install on ubuntu
查看>>
Android的Handler,Message,Looper的原理详解
查看>>
scala(1):基础知识
查看>>
Navicat生成ER图表的教程
查看>>
《android用SAX解析xml》
查看>>
linux中的脏页写回
查看>>
一次线上小问题的思考
查看>>
Java逐行输出N*N螺旋矩阵,要求最小空间复杂度
查看>>
Java多线程 - 线程 和 任务
查看>>
关于MVC框架的学习
查看>>
ubuntu12.04下hadoop单机模式和伪分布模式环境搭建
查看>>
Spark之RDD持久化、广播、累加器
查看>>
业务系统获取来源IP的正确姿势
查看>>