mobx响应原理以及源码调试

1,302 阅读13分钟

调试的准备工作

项目地址 github.com/hahaliangch…

创建一个tsreact-app项目
mobx源码移入到新创建的react-app项目 注意是ts的项目
因为我们的tsreact项目的tsconfig文件配置有isolatedModules这一项我们就需要将mobx源码所有导出的类型 重新export type {}方式导出一遍如图所示:

image.png isolatedModules这一编译选项最好不要设置false或者删除虽然这样就不必重新导出类型了

接着tsconfig仍需要添加这几项:

//在tsconfig.json内compilerOptions中
 compilerOptions: {
     "noImplicitAny": false,
    "noImplicitThis":false,
    "downlevelIteration": true,
    
  }

添加以上三项分别是noImplicitAny隐式any类型设置false为不报错允许通过,noImplicitThis,设置false不严格检查this的隐式类型,downlevelIteration,降低迭代器版本,迭代器生成器函数返回或一些js的数据结构详情请看js相关资料,由于新语言特性的迭代器和旧的不同,为了让旧的可以运行就必须降低迭代器版本。

更改源码,将Lambda类型更改为:

   export interface Lambda {
    (value?:any ):void
    name?: string
} 

ownkeys返回类型由PropertyKey变为string|symbol
(在dynamicobject.ts、observableobject.ts和utils.ts中) IMapDidChange<unknown, unknown>改成IMapDidChange<unknown|any, unknown|any>(在spy.ts中)

做完以上几项基本也就可以开始调试了

mobx程序逻辑概述

mobx响应原理的关键

响应的系统的简单实现是依赖于Object.definePropertyProxy

Object.defineProperty用于属性类型是基本类型(number,string,boolean等)对其进行设置,一般设置该属性的get,set,以用来在读取和赋值时应执行哪些操作
Proxy同样是对对象属性的get,set进行操作,但不同是Proxy用于对于复合对象的操作响应的实现如Array,map,set等类型。

响应式的优点

看下图

image.png

当没有使用响应时,修改obj.a的值与其在改变时应该执行的逻辑需要放在同一个域内, 这表示当只要修改obj.a时就需要在其域内写上改变时执行的对应逻辑,这样使得每次改变都需要在下面写上改变的逻辑,非常麻烦且容易出错。
当使用响应时,我们只需在一处写改变时执行的逻辑就可以了,当obj.a被改变时自动执行设置好的对应方法,我们就不需要时刻提醒自己在改变obj.a时应该执行哪些方法。

mobx响应系统概述

首先看使用示例

image.png

this为传入的对象,方法的第二参数是一个配置对象,左边属性是传入的this里所有的属性,右边是对改属性的操作,也被成为对该属性的修饰。 这里先说下observable它是响应式的关键之一,经由它开始对修饰属性设置get,set

这里简要说下mobx的四个个基本功能模块:

  1. 修饰模块 上图中在属性后面的各个值,在mobx中称为修饰,这里称它为修饰模块常用到的便是代码示例所展示的
  2. 管理模块 ObservableObjectAdministration makeObservable内部直接创建一个ObservableObjectAdministration对象,然后用它调用修饰功能
  3. 真实功能模块 它提供被设置get,set时所绑定执行的具体逻辑,也是mobx响应功能的核心,读取时执行什么,写入时执行什么都在该模块中。
  4. 反应模块 reaction mobx提供给用户用来自定义响应时应该执行什么的功能模块,它被真实功能模块所连接,所以在响应时得以执行

reaction 使用示例

image.png

reaction方法中有两个参数,分别是读取被observable修饰的属性,该属性被改变时执行的逻辑 所以当testMobx.value被改变时 下面就会打印出被改变的值以及'获取响应'。
重要一点是当第一参数中没有读取testMobx.value或且没有返回testMobx.value,第二参数将不会执行

这也是mobx的响应系统的一种特点,读取时注册,写入时响应

mobx响应实现的基本流程

我们从最简单流程入手,也就是设置响应的属性值为基本类型时(string,number,boolean...)的过程

class Doubler {
    value=0
    array=[]
    constructor(value) {
        makeObservable(this, {
            value: observable,
            array:observable,
            double: computed,
            increment: action,
        })
       
    }
    ...
    }

也就是上述示例代码中对value属性设置响应的过程,value是number类型

先看一下 ObservableObjectAdministration内几个关键方法 make_ 它由makeObservable调用

image.png 它会调用修饰的make_方法 这里我们先只看响应的实现所以annotation当做observable

来到observable这里,来看它的定义

export var observable: IObservableFactory = assign(createObservable, observableFactories)

可以得知它是由createObservable, observableFactories两个合并而来
我们从最简单流程入手,这里关键方法为createObservable

继续看

Object.assign(createObservable, observableDecoratorAnnotation)

看源码我们得知createObservableobservableDecoratorAnnotation接下来的关键是observableDecoratorAnnotation

image.png 直接看createObservableAnnotation

function createObservableAnnotation(name: string, options?: object): Annotation {
    return {
        annotationType_: name,
        options_: options,
        make_,
        extend_
    }
}

注意make_ObservableObjectAdministration调用的就是此方法

function make_(
    adm: ObservableObjectAdministration,
    key: PropertyKey,
    descriptor: PropertyDescriptor
): MakeResult {
    return this.extend_(adm, key, descriptor, false) === null ? MakeResult.Cancel : MakeResult.Break
}

make_又调用了extend_

function extend_(
    adm: ObservableObjectAdministration,
    key: PropertyKey,
    descriptor: PropertyDescriptor,
    proxyTrap: boolean
): boolean | null {
    assertObservableDescriptor(adm, this, key, descriptor)
    //console.log('设置可响应')
    return adm.defineObservableProperty_(
        key,
        descriptor.value,
        this.options_?.enhancer ?? deepEnhancer,
        proxyTrap
    )
}

直接看重点adm.defineObservableProperty_这里的adm就是当执行 const outcome = annotation.make_(this, key, descriptor, source)时传入的this便是adm

adm.defineObservableProperty_方法的核心便是调用Object.defineProperty 内部主要过程如下
先是获取被修饰属性的描述符

   const cachedDescriptor = getCachedObservablePropDescriptor(key)
            const descriptor = {
                configurable: globalState.safeDescriptors ? this.isPlainObject_ : true,
                enumerable: true,
                get: cachedDescriptor.get,
                set: cachedDescriptor.set
            }
            
            
  defineProperty(this.target_, key, descriptor)

注意看这里defineProperty实际就是Object.defineProperty, 而设置属性的get,set方法从cachedDescriptor取得

继续向下

 //这里创建observableValue
            const observable = new ObservableValue(
                value,
                enhancer,
                __DEV__ ? `${this.name_}.${key.toString()}` : "ObservableObject.key",
                false
            )
            this.values_.set(key, observable)

this.value_是一个Map类型实例,由ObservableObjectAdministration被实例化传入,由上述代码可知他将被修饰的key以及observable存入此map中,这个this.value_有什么用呢?

看上面代码中的getCachedObservablePropDescriptor

function getCachedObservablePropDescriptor(key) {
    //descriptorCache Object.create(null)创建出来没有原型的纯{}对象
    //this是用户自定义的对象,this[$mobx]是ObservableObjectAdministration对象内部value_是Map存储this的一些被annotation修饰过的key,以及相对应的对象如ObserValue
    return (
        descriptorCache[key] ||
        (descriptorCache[key] = {
            get() {
                return this[$mobx].getObservablePropValue_(key)//响应系统关键点
            },
            set(value) {
                return this[$mobx].setObservablePropValue_(key, value)
            }
        })
    )
}
 getObservablePropValue_(key: PropertyKey): any {
        return this.values_.get(key)!.get()
    }
 setObservablePropValue_(key: PropertyKey, newValue): boolean | null {
        const observable = this.values_.get(key)
       //省略一些代码来看主要部分
       (observable as ObservableValue<any>).setNewValue_(newValue)
       ...
       

由此我们可以得知对于示例代码 属性value的绑定,对其设置get,set的方法都由observable提供,而this.values_存储了它

以上流程可以简要概况为,ObservableObjectAdministration调用自身make_方法,之后用被调用了自身defineObservableProperty_实现了对被修饰keyget,set的设置

而设置get,set的方法内部都做了哪些事?接下来深入了解observable

实体功能模块 ObservableValue

前面所说实体功能模块提供的系统的功能核心,也知道变量observable提供了get,set时需要执行的方法。接下来来看这两个方法get,setNewValue_。 首先是get:

 public get(): T {
        this.reportObserved()
        return this.dehanceValue(this.value_)
    }

this.dehanceValue(this.value_)实际上返回this.value_ObservableObjectAdministration调用自身方法defineObservableProperty_创建observable示例得来

 const observable = new ObservableValue(
                value,
                enhancer,
                __DEV__ ? `${this.name_}.${key.toString()}` : "ObservableObject.key",
                false
            )

第一参value赋值给内部的value_属性,而这个value是通过获取修饰属性的描述符得来的代码在ObservableObjectAdministration.make_中,const descriptor = getDescriptor(source, key) 实际上掉用的是Object.getOwnPropertyDescriptor
这个value本质上就是被修饰属性的值,mobx使用示例代码中被修饰属性是value,值为2,将属性的值存入到了observable。也就是说当此被修饰的属性读取时,获取到的是observable.value_

接下来我们看关键的this.reportObserved(),如何与reaction建立连接,靠的就是它,上面说过mobx是读取时注册,写入时响应,在reaction示例代码中就有体现,当我们在第一参读取时,实际就把第二参的方法绑定在被修饰属性set中。

function reportObserved(observable: IObservable): boolean {
    checkIfStateReadsAreAllowed(observable)
    const derivation = globalState.trackingDerivation
    if (derivation !== null) {
        /**
         * Simple optimization, give each derivation run an unique id (runId)
         * Check if last time this observable was accessed the same runId is used
         * if this is the case, the relation is already known
         */
        //经过测试如果想要响应的变量如果没在reaction的第一个参数返回出来即使是读了一遍
       //也不响应判断逻辑为之下 globalState.trackingContext是reaction
        if (derivation.runId_ !== observable.lastAccessedBy_) {
            observable.lastAccessedBy_ = derivation.runId_
            // Tried storing newObserving, or observing, or both as Set, but performance didn't come close...
            derivation.newObserving_![derivation.unboundDepsCount_++] = observable
            if (!observable.isBeingObserved_ && globalState.trackingContext) {
                observable.isBeingObserved_ = true
                observable.onBO()
            }
        }
    
        return true
    } else if (observable.observers_.size === 0 && globalState.inBatch > 0) {
        queueForUnobservation(observable)
    }

    return false
}

这里注意看const derivation = globalState.trackingDerivation 然后再看derivation.newObserving_![derivation.unboundDepsCount_++] = observable,我们看到正在执行读取操作的属性,其observable被放入了derivation.newObserving_数组中。

reaction的第一参数中读取被observable修饰的属性才能对这个属性进行监听响应原因在于这里,当reaction执行时它会把自身赋值给globalState.trackingDerivation,这时执行第一参的方法,就完成了注册。(reaction获取到observable)
reaction内部实例化一个Reaction的对象 并调用track方法,接着方法内部执行了globalState.trackingContext = this

反应模块reaction

接下来到属性赋值时响应功能的解析了,由上述当被修饰的属性在被赋值写入时,执行的是observable.setNewValue_
现在我们知道对象Reaction的实例有一个newObserving_属性类型为数组储存了在reaction内被读取的Observable
我们不妨设想一下写入时响应应该是如何实现
如果紧靠调用reaction存储的Observable实现响应行不行?也就是说当observable.setNewValue_执行时仅靠遍历newObserving_中的observable?
然后判断这时发生的响应observable是谁,然后再执行写入reaction的第二参,执行用户自定义的响应逻辑,貌似可行。
但存在一个致命问题,在上述情况下,当reaction只有一个时,是可行的,但如果有多个,那又如何去寻找被写入的observable在哪个reaction,设置一个全局reaction数组?,那维护词此数组所带来的代价有点大,尤其为了一个observable响应,去遍历所有存在的reaction,未免有些过于浪费

mobx在读取时注册依赖,写入时响应依赖

将关注点集中在observable上,当setNewValue时,为何不去在observable上找哪个reaction与其绑定,还有一点绑定这个observablereaction会只有一个么?想想也当然不会。
所以observable上必然有一个属性用来存储reaction类似于Reaction实例对象上newObserving_存储observable一样。
不过说回来为什么newObserving_是一个数组,而不是一个普通对象类型属性只存储一个observable? 因为在reaction第一参中的方法内,不一定只读取一个被observable修饰的属性
observable.set方法中,我们只看到了observablereaction收入囊中,而reaction并未被observable收集,那收集的逻辑在哪里呢?
当然不会在setNewValue_中在那里绑定也太晚了,而且去寻找绑定的reaction的逻辑就会和我上述所说的一样
既然在observable.set在执行时将自身给了reaction,那同样reaction在执行自身逻辑时,应该也一样把自身给observable实现双向绑定,响应依赖的核心其实仍是observable遍历自己存储的reaction,而reaction的作用就是将自己绑定在observable

接下来我们看reaction内部的逻辑

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

创建了Reaction对象,第二参是个方法,必然由React对象自行调用,关键在于reactionRunner方法 它同样定义在reaction方法内

 function reactionRunner() {//在set时会再度调用此方法 runReactionsHelper会调用Reaction中runReaction_方法进而调用这里
        isScheduled = false
        if (r.isDisposed_) return
        let changed: boolean = false
        r.track(() => {
            //将globalState.allowStateChanges设置为false 意思是以下操作不会修改状态
            //如果在reaction的读取操作中,未将响应值返回出来此处得不到值只会获取到undifined新值旧值一直相等就不会调用响应逻辑
            const nextValue = allowStateChanges(false, () => expression(r))
            changed = firstTime || !equals(value, nextValue)//用来判断新的值与旧的值是否相等是否发生改变
            oldValue = value
            value = nextValue
            console.log(changed,'changed','r.track内部代码被执行')
        })
        if (firstTime && opts.fireImmediately!) {
            //初始reaction并未执行 调用什么方法才能让if或者else执行,难道是autorun
            effectAction(value, oldValue, r)
        } 
        else if (!firstTime && changed){
             //初始reaction并未执行,在set时会执行到这
            effectAction(value, oldValue, r)
        } 
        firstTime = false
    }

    r.schedule_()

这里expression(r)effectAction分别是reaction方法的第一参和第二参,它被放在r.track方法中执行,我们可以看出第一参只有在reaction被调用时执行,因为firstTime的限制第二参不会执行。 由 r.schedule_()调用这里reactionRunner方法
这里有一个重点方法便是r.track,顾名思义我们也大概猜出它是来干什么的。

 track(fn: () => void) {
      
        globalState.trackingContext = this
        const result = trackDerivedFunction(this, fn, undefined)//reaction响应关键函数
    }

track方法目前我们只关注上面两行代码globalState.trackingContext = this上面已经说过了,接下来便是 trackDerivedFunction

function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context: any) {
    bindDependencies(derivation)
  
}

我们只关注bindDependencies

function bindDependencies(derivation: IDerivation) {
    // invariant(derivation.dependenciesState !== IDerivationState.NOT_TRACKING, "INTERNAL ERROR bindDependencies expects derivation.dependenciesState !== -1");
    const prevObserving = derivation.observing_
    const observing = (derivation.observing_ = derivation.newObserving_!)
    let lowestNewObservingDerivationState = IDerivationState_.UP_TO_DATE_

    // Go through all new observables and check diffValue: (this list can contain duplicates):
    //   0: first occurrence, change to 1 and keep it
    //   1: extra occurrence, drop it
    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++
        }

        // Upcast is 'safe' here, because if dep is IObservable, `dependenciesState` will be undefined,
        // not hitting the condition
        if (((dep as any) as IDerivation).dependenciesState_ > lowestNewObservingDerivationState) {
            lowestNewObservingDerivationState = ((dep as any) as IDerivation).dependenciesState_
        }
    }
    
    observing.length = i0//调整为observableValue真实存在的数量
    derivation.newObserving_ = null // newObserving shouldn't be needed outside tracking (statement moved down to work around FF bug, see #614)

    // Go through all old observables and check diffValue: (it is unique after last bindDependencies)
    //   0: it's not in new observables, unobserve it
    //   1: it keeps being observed, don't want to notify it. change to 0
    l = prevObserving.length
    
    while (l--) {
        const dep = prevObserving[l]
        if (dep.diffValue_ === 0) {
         
            removeObserver(dep, derivation)
        }
        dep.diffValue_ = 0
    }

    // Go through all new observables and check diffValue: (now it should be unique)
    //   0: it was set to 0 in last loop. don't need to do anything.
    //   1: it wasn't observed, let's observe it. set back to 0
    while (i0--) {
        const dep = observing[i0]
        if (dep.diffValue_ === 1) {
            dep.diffValue_ = 0
           addObserver(dep, derivation)
        }
    }

    // Some new observed derivations may become stale during this derivation computation
    // so they have had no chance to propagate staleness (#916)
    if (lowestNewObservingDerivationState !== IDerivationState_.UP_TO_DATE_) {
        derivation.dependenciesState_ = lowestNewObservingDerivationState
        derivation.onBecomeStale_()
    }
}

这里将完整代码展示出来,可以看出此方法的主要作用就是遍历此reaction存储的所有的observable,对于旧的且不响应的将它解除绑定,对于新的就添加绑定,这里我们看addObserver

function addObserver(observable: IObservable, node: IDerivation) 
    observable.observers_.add(node)//Set结合
}

由此断定了我们的猜想,在reaction内部逻辑中存在它将自己绑定在observable
接下来我们便可以大胆猜测observable.setNewValue中主要的作用就是遍历observable中的reaction 并执行在reaction第二参,也就是用户自定义的响应时应该做什么的逻辑

响应的实现

直接看Observable.setNewValue

 setNewValue_(newValue: T) {
        const oldValue = this.value_
        this.value_ = newValue
        this.reportChanged()//触发reaction
        if (hasListeners(this)) {
            notifyListeners(this, {
                type: UPDATE,
                object: this,
                newValue,
                oldValue
            })
        }
    }

这里我们只看reportChanged 会看前面的observable.get 你会发现它与getreportObserved相对应, 这两个方法正是读取时注册依赖,写入时响应依赖

public reportChanged() {
        startBatch()
        propagateChanged(this)
        endBatch()
    }

这里注意下startBatchendBatch,你会发现在mobx源码中很多地方用到它,startBatch内部逻辑很简单就一句 globalState.inBatch++,相应的 endBatch会执行globalState.inBatch--,而在endBatch中执行了响应

startBatchendBatch是mobx注释action的关键所在 我们先看下propagateChanged(this)

function propagateChanged(observable: IObservable) {
    // invariantLOS(observable, "changed start");
    if (observable.lowestObserverState_ === IDerivationState_.STALE_) return
    observable.lowestObserverState_ = IDerivationState_.STALE_

    // Ideally we use for..of here, but the downcompiled version is really slow...
    observable.observers_.forEach(d => {
        if (d.dependenciesState_ === IDerivationState_.UP_TO_DATE_) {
            if (__DEV__ && d.isTracing_ !== TraceMode.NONE) {
                logTraceInfo(d, observable)
            }
            d.onBecomeStale_()//这里调用Reaction.schedule_添加调用reaction的栈
        }
        d.dependenciesState_ = IDerivationState_.STALE_
    })
    // invariantLOS(observable, "changed end");
}

遍历observable.observers_内存储的Reaction这和我们之前预想的setNewValue的执行逻辑吻合 注意 d.onBecomeStale_(),实际调用了,Reaction内部schedule_

 onBecomeStale_() {
        this.schedule_()
    }

    schedule_() {
        if (!this.isScheduled_) {
            this.isScheduled_ = true
            globalState.pendingReactions.push(this)
            runReactions()//它会用runReactionsHelper遍历globalState.pendingReactions内的Reaction并执行runReaction_
            //调度调用此函数实际上会调用onInvalidate而在reaction方法中会调用track方法
        }
    }

globalState.pendingReactions.push(this) 嗯和在get时用globalState.trackingContext存储reaction方式一样,所以接下来会遍历globalState.pendingReactions内的reaction以执行自定义响应逻辑,来看 endBatch

function endBatch() {
   
    if (--globalState.inBatch === 0) {
        console.log('开始调用reaction')
        runReactions()
        // the batch is actually about to finish, all unobserving should happen here.
        const list = globalState.pendingUnobservations
        for (let i = 0; i < list.length; i++) {
            const observable = list[i]
            observable.isPendingUnobservation_ = false
            if (observable.observers_.size === 0) {
                if (observable.isBeingObserved_) {
                    // if this observable had reactive observers, trigger the hooks
                    observable.isBeingObserved_ = false
                    observable.onBUO()
                }
                if (observable instanceof ComputedValue) {
                    // computed values are automatically teared down when the last observer leaves
                    // this process happens recursively, this computed might be the last observabe of another, etc..
                    observable.suspend_()
                }
            }
        }
        globalState.pendingUnobservations = []
    }
}

会看到只有当--globalState.inBatch === 0runReactions(),所以runReactions 是可以延迟执行的

runReactions执行了runReactionsHelper

function runReactionsHelper() {
    globalState.isRunningReactions = true
    const allReactions = globalState.pendingReactions//由Reaction中schedule_添加Reaction
    let iterations = 0
    // While running reactions, new reactions might be triggered.
    // Hence we work with two variables and check whether
    // we converge to no remaining reactions after a while.
    while (allReactions.length > 0) {
        if (++iterations === MAX_REACTION_ITERATIONS) {
            console.error(
                __DEV__
                    ? `Reaction doesn't converge to a stable state after ${MAX_REACTION_ITERATIONS} iterations.` +
                          ` Probably there is a cycle in the reactive function: ${allReactions[0]}`
                    : `[mobx] cycle in reaction: ${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
}

显而易见遍历globalState.pendingReactions内的reaction并执行其runReaction_方法。到此便验证了我们之前预想的setNewValue的执行逻辑完全吻合
runReaction_方法

runReaction_() {
        if (!this.isDisposed_) {
            startBatch()
            this.isScheduled_ = false
            const prev = globalState.trackingContext
            globalState.trackingContext = this
            if (shouldCompute(this)) {
                this.isTrackPending_ = true

                try {
                    this.onInvalidate_()
                    if (__DEV__ && this.isTrackPending_ && isSpyEnabled()) {
                        // onInvalidate didn't trigger track right away..
                        spyReport({
                            name: this.name_,
                            type: "scheduled-reaction"
                        })
                    }
                } catch (e) {
                    this.reportExceptionInDerivation_(e)
                }
            }
            globalState.trackingContext = prev
            endBatch()
        }
    }

这里我们只关注this.onInvalidate_(),这个是由实力化Reaction时第二参传入的,而这第二参便是在reaction内的reactionRunner

 function reactionRunner() {//在set时会再度调用此方法 runReactionsHelper会调用Reaction中runReaction_方法进而调用这里
        isScheduled = false
        if (r.isDisposed_) return
        let changed: boolean = false
        r.track(() => {
            //将globalState.allowStateChanges设置为false 意思是以下操作不会修改状态
            //如果在reaction的读取操作中,未将响应值返回出来此处得不到值只会获取到undifined新值旧值一直相等就不会调用响应逻辑
            const nextValue = allowStateChanges(false, () => expression(r))
            changed = firstTime || !equals(value, nextValue)//用来判断新的值与旧的值是否相等是否发生改变
            oldValue = value
            value = nextValue
            console.log(changed,'changed','r.track内部代码被执行')
        })
        if (firstTime && opts.fireImmediately!) {
            //初始reaction并未执行 调用什么方法才能让if或者else执行,难道是autorun
            effectAction(value, oldValue, r)
        } 
        else if (!firstTime && changed){
             //初始reaction并未执行,在set时会执行到这
            effectAction(value, oldValue, r)
        } 
        firstTime = false
    }

此时因为reactionRunner已经不是第一次执行(第一次执行是在reaction方法调用时执行)所以就会执行 effectAction这是我们自定义的响应逻辑
reaction使用的示例代码,其中value=>{console.log(value,'获取响应')便是effectAction

reaction((r)=>{
    console.log(testMobx.value,'测试全局state存入的reaction')
    //未将响应值返回出来此处得不到值只会获取到undifined新值旧值一直相等就不会调用响应逻辑
    return testMobx.value},value=>{console.log(value,'获取响应')/* 此方法自动被action包裹执行action名为Reaction */})

细心会发现,每次响应都会执行 r.track r.track是注册依赖,那么重复调用会不会造成reaction重复, 在bindDependencies方法中,每一个observable都有个属性diffValue_当它是0时证明不在响应,应该被丢弃,当它是1时证明是新的observable则添加,而我们此时的observable在一种尴尬的情境,它既不是新的,也不是不响应了。
所以bindDependencies先执行删除当diffValue_为0时执行removeObserver(dep, derivation),不为0时将它置为0,下面便执行添加,当是1时才添加,所以依赖不会重复注册。

代码详情

 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
           addObserver(dep, derivation)
        }
    }

以上便是mobx响应系统实现的基本逻辑

mobx的其他需要学习的功能代码

action注释

根据上面所讲述mobx的基本响应过程,我们发现每次响应时,都会将与该observable所绑定的所有reaction遍历执行,注意看响应过程中的源码,它并不是简单将observablereaction直接取出遍历,而是通过由Reactionschedule_添加ReactionglobalState.pendingReactions。这样做的好处显而有一个,那就是去除了runReactionobservable直接的耦合,如果不这样那么当observable响应时,runReaction就必然会立刻调用
我们设想这样一种情况当在一个函数域中我们将一个observable修饰的属性接连改变两次或多次

   increment() {
        this.value++ //被observable修饰过的属性
        ...
        this.value++
    }

我们有时察觉不到同一个属性在一个函数域中调用两次,或是一种更常见的情景两个接连被调用的方法都改变了this.value的值,当然我们需要取最新的改变值。如果用上面所知道的基本流程推理,我们很容易知道,每一次改变都需要遍历reaction,而这种情况我们就需要重复的遍历多次,这样浪费性能,实际上我们仅需要遍历一次就足够了
上面所说globalState.pendingReactions的作用消除了observablerunReaction直接的耦合,这样最直接的好处就是当observable开始响应时,runReaction可以稍缓执行,假设直接遍历observable里头的reaction并执行其中runReaction是做不到这一点的。 所以action大概做了件什么事,也有数了,接下来看代码

function executeAction(
    actionName: string,
    canRunAsDerivation: boolean,
    fn: Function,
    scope?: any,
    args?: IArguments
) {
    const runInfo = _startAction(actionName, canRunAsDerivation, scope, args)
    try {
        return fn.apply(scope, args)
    } catch (err) {
        runInfo.error_ = err
        throw err
    } finally {
        _endAction(runInfo)
    }
}

这里fn是被action修饰的方法,如果翻到最前面的示例代码被action包裹的方法是increment 很明显的逻辑先是startAction然后执行我们被包裹的方法,最后endAction
等等没发现startActionendAction组合方式貌似很眼熟么?
没错正是startBatchendBatch。观看startAction源码发现其内部执行了startBatch,这也就意味着在其内部执行的任何响应都不会触发runReaction的调用,除非当action执行结束调用_endAction。 所以我们立马能想到在_endAction调用了endBatch,由于在action包裹的函数无论对被修饰属性做了多少写入操作,由于再执行endBatch时其判断 if (--globalState.inBatch === 0)都不会成立,也就不会执行runReaction,原因是action再其内部方法执行前先调用了startBatchinBatch加了1。
这最直接的体现就是加入我们多次更改被修饰属性值,实际上只调用一次,这在绝大多数情况下都是我们想要的,因为实在没必要多次重复调用。
这就是action的作用,也是之前所说startBatchendBatch是action核心的原因

对于复合类型array,set,map实现响应系统的方式

上面所讲的响应流程及其原理,拿最基本的类型的属性举例,实际上复合类型的属性要复杂些,但最终仍是走这套响应流程,所以先从最基本入手,才能更深入的去研究这类复合类型响应的实现。 我们最常见的复合类型就是数组了,实际上对于数组的处理方式和set,map一样,所以这里就只拿数组array举例。
我们现在简单设想一下对于被赋值为数组的属性,其响应和上述的基本响应一样,问题在于数组内部
对于数组我们希望的是,无论是其内部元素改变,或是数组长度发生改变都要去触发响应,这也可以说我们最长对数组操作的两种方法如下标赋值或是执行push都会触发响应。
所以如何实现呢?
由于数组是js中原生存在的数据结构,所以不能对其进行defineProperty,所以我们使用Proxy
首先我们知道响应系统的运行原理时读取时注入依赖,写入时响应依赖,分别对应两个关键方法reportObservedreportChanged
读取时注册依赖这点倒是容易实现,只要是数组的属性被读取我们就执行reportObserved
但如果数组执行push操作时,我们该如何响应呢?在执行push之前我们知道它会读取一次push属性,难道在读取时响应依赖?万一它仅仅只是读取,而不执行呢?
所以最简单的逻辑就是我们代理push方法。

简单的示例代码
 push(value){
 target.push(value)
 reportChanged(this)
 }

现在开始验证以上想法
我们知道修饰属性的注解observable会被ObservableObjectAdministration调用其make_方法然后调用extend_回到ObservableObjectAdministration.defineObservableProperty_,看过observable源码我们知道observable是个function但目前来看只调用了它make_方法和extend_那其其他功能属性再哪调用呢? 答案是在ObservableObjectAdministration.defineObservableProperty_内创建ObservableValue对象时调用, 关键代码

  this.value_ = enhancer(value, undefined, name_)//enhancer是deepEnhancer
function deepEnhancer(v, _, name) {
    
    // it is an observable already, done
    if (isObservable(v)) return v

    // something that can be converted and mutated?
    if (Array.isArray(v)) return observable.array(v, { name })
    if (isPlainObject(v)) return observable.object(v, undefined, { name })
    if (isES6Map(v)) return observable.map(v, { name })
    if (isES6Set(v)) return observable.set(v, { name })
    if (typeof v === "function" && !isAction(v) && !isFlow(v)) {
        if (isGenerator(v)) {
            return flow(v)
        } else {
            return autoAction(name, v)
        }
    }
    return v
}

这里enhancer调用了Observable其他功能属性(既是对各种类型的处理方式)如何是基本类型则直接返回,对于数组则直接调用createObservableArray(通过observable.array

function createObservableArray<T>(
    initialValues: T[] | undefined,
    enhancer: IEnhancer<T>,
    name = __DEV__ ? "ObservableArray@" + getNextId() : "ObservableArray",
    owned = false
): IObservableArray<T> {
    assertProxies()
    const adm = new ObservableArrayAdministration(name, enhancer, owned, false)
    addHiddenFinalProp(adm.values_, $mobx, adm)
    const proxy = new Proxy(adm.values_, arrayTraps) as any
    adm.proxy_ = proxy
    if (initialValues && initialValues.length) {
        const prev = allowStateChangesStart(true)
        adm.spliceWithArray_(0, 0, initialValues)
        allowStateChangesEnd(prev)
    }
    console.log(adm.values_,'看看数组信息')
    return proxy
}

这里我们看到对数组使用Proxy并返回被代理后的数组,这里的我们先看arrayTraps

const arrayTraps = {
    get(target, name) {
        const adm: ObservableArrayAdministration = target[$mobx]
        if (name === $mobx) return adm
        if (name === "length") return adm.getArrayLength_()
        if (typeof name === "string" && !isNaN(name as any)) {
            return adm.get_(parseInt(name)) //数组下标的访问形式
        }
        if (hasProp(arrayExtensions, name)) {
            return arrayExtensions[name]//数组方法的访问形式
        }
        return target[name]
    },
    set(target, name, value): boolean {
        const adm: ObservableArrayAdministration = target[$mobx]
        if (name === "length") {
            
            adm.setArrayLength_(value)
        }
        if (typeof name === "symbol" || isNaN(name)) {
            target[name] = value
        } else {
            // numeric string
            
            adm.set_(parseInt(name), value)
        }
        return true
    },
    preventExtensions() {
        die(15)
    }
}

对数组get,set的设置,这里我们看arrayExtensions,看过源码(代码量太大这里就不贴了)我们会知道它类似于上述写的push方法一样,它对数组的所有方法都进行了代理实现,也就是说我们无论执行数组什么方法都会执行其提供的方法
createObservableArray有最重要一点const adm = new ObservableArrayAdministration(name, enhancer, owned, false) 创建了ObservableArrayAdministration对象实际上arrayExtensions提供的方法都会调用ObservableArrayAdministration方法

class ObservableArrayAdministration
    implements IInterceptable<IArrayWillChange<any> | IArrayWillSplice<any>>, IListenable {
    atom_: IAtom
    readonly values_: any[] = [] // this is the prop that gets proxied, so can't replace it!
    interceptors_
    changeListeners_
    enhancer_: (newV: any, oldV: any | undefined) => any
    dehancer: any
    proxy_!: IObservableArray<any>
    lastKnownLength_ = 0
    
    ...
    
    }

这里我们看values_属性,他是只读的实际上对于数组的任何操作都是操作它
atom属性类型是IAtom

 constructor(
        name = __DEV__ ? "ObservableArray@" + getNextId() : "ObservableArray",
        enhancer: IEnhancer<any>,
        public owned_: boolean,
        public legacyMode_: boolean
    ) {
        this.atom_ = new Atom(name)
        this.enhancer_ = (newV, oldV) =>
            enhancer(newV, oldV, __DEV__ ? name + "[..]" : "ObservableArray[..]")
    }

在实例时被赋值
Atom类型我们之间提到的ObservableValue便是继承于它,内部属性 observers_存储绑定的reaction, 两个关键方法reportObservedreportChanged
所以我们知道当对数组操作时都会由 ObservableArrayAdministration代理执行,然后恰当的使用this.atom_.reportObservedthis.atom_.reportChange来完成注册和响应。