Mobx 源碼閱讀簡記

Mobx 源碼簡記

總體會寫得比較亂,同時也比較簡單,和讀書筆記差很少,基本是邊讀邊寫。見諒~javascript

主要三大部分AtomObservableDerivationjava

Atom

Mobx的原子類,可以被觀察和通知變化,observableValue繼承於Atom。observableValue ---> Atomreact

同時裏面有幾個比較重要的屬性與方法。數組

  • 屬性安全

    • observers,用於存放這個被原子類被誰觀察了,是一個set結構
    • diffValue,後續更新依賴的時候要用這個來判斷
  • 方法ide

    • reportObserved,調用全局的reportObserved函數,通知自身被觀察了
    • reportChanged,調用全局的propagateChanged函數,通知自身發生變化了

Observable

Observable是一個工廠函數,讓數據變得可觀察。這個東西須要和上述的Atom創建聯繫,即將具體的Atom聯繫起來。從而打通自身可以被觀察,同時能通知變化的整個流程。函數

三種可被觀察的數據類型:對象,數組,Map,下面簡單介紹如何實現。假如都不是,就會提示用戶調用observable.box,使其擁有get,set方法,即上述說的observableValue數據類型。優化

部分代碼以下:ui

fucntion Observable(v) {
    // 若是已被觀察,直接返回
    if (isObservable(v)) return v

    // 根據其類型分別調用observable.object、observable.array、observable.map
    const res = isPlainObject(v)
        ? observable.object(v, arg2, arg3)
        : Array.isArray(v)
            ? observable.array(v, arg2)
            : isES6Map(v)
                ? observable.map(v, arg2)
                : v

    // 返回被觀察過的東西
    if (res !== v) return res
    
    // 都不是,提示用戶調用observable.box(value)
    fail(
        process.env.NODE_ENV !== "production" &&
            `The provided value could not be converted into an observable. If you want just create an observable reference to the object use 'observable.box(value)'`
    )
}

重點是observable.object、observable.array、observable.map三者的實現,下面是討論關於對象的實現方式this

  • 對象(observable.object)

    • 先建立一個base對象,咱們稱爲adm對象。同時,根據這個base對象建立一個proxy,會經過該proxy將會對原對象的各類值進行代理,而adm[$mobx]指向該一個新建的ObservableObjectAdministration數據類型
    • 對傳進來的props(即須要被觀察的對象),會先尋找是否有get屬性(即計算屬性),有的話會建立一個計算屬性代理,並和其他的屬性一塊兒掛載在該proxy上
    • 有計算屬性時,會新建一個既有observableValue也有derivation屬性的computedValue類,存放到adm[$mobx].values裏面,key就是computed的key

      • 同時會拿到它的get函數,做爲這個derivation的監聽函數,進行初始化監聽
      • 並經過Object.defineProperty設置了該屬性的get和set屬性
    • 其他的屬性,會新建一個observableValue,存放到adm[$mobx].values裏面,並經過Object.defineProperty設置了該屬性的get和set屬性
    • 而後,重點是建立proxy時的handler對象的getset函數,在有新屬性訪問時或改變值時會調用getset函數

      • 訪問新屬性時,get函數會讀取adm[$mobx],若是沒有,會經過has方法,創建一個**observableValue**,並放到adm[\$mobx].pendingKeys中(還不知道有什麼用)
      • 設置新屬性時,會新建一個observableValue存放進去adm[$mobx].values中,同時,經過Object.defineProperty設置了該屬性的get和set屬性

重點:

(observableValue簡稱爲oV,Object.defineProperty簡稱爲Od)

  • 上面說的全部經過Od定義後的set會調用已存放的oVset,get會調用已存放的oVget
  • 第一點說過oV繼承於Atom,因此oVset會調用reportChanged,oVget會調用reportObserved

這樣子,整個對象屬性的監聽流程就創建起來了

Reaction

Reaction(反應)是一類特殊的Derivation,能夠註冊響應函數,使之在條件知足時自動執行。使用以下:

// new Reaction(name, onInvalidate)
const reaction = new Reaction('name', () => {
    // do something,即響應函數,發生反作用的地方
    console.log('excuted!')
})

const ob = observable.object({
    name: 'laji',
    key: '9527'
})

reaction.track(() => {
    // 註冊須要被追蹤的變量,這裏訪問了已經被觀察的ob對象,因此當ob.name或ob.key發生改變時,上面的響應函數會執行
    console.log(`my name is ${ob.name}`)
    console.log(`${ob.key} hey hey hey!`)
})

ob.name = 'mike' // 'excuted!'

讓咱們分析一下源碼實現,主要有幾個重點:

  • 初始化Reaction類時,會將onInvalidate函數存儲起來
  • 在調用track函數時,這個是重點,會調用trackDerivedFunction(this, fn, undefined)

    • trackDerivedFunction這個函數,就是依賴收集,即註冊須要被追蹤的變量,它會作幾件事情,看下面註釋
    export function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context: any) {
        // 將該 Derivation 的 dependenciesState 和當前全部依賴的 lowestObserverState 設爲最新的狀態
        changeDependenciesStateTo0(derivation)
        // 創建一個該derivation新的newObserving數組,裏面存放的是誰被該derivation註冊依賴了
        derivation.newObserving = new Array(derivation.observing.length + 100)
        // 記錄新的依賴的數量
        derivation.unboundDepsCount = 0
        // 每次執行都分配一個全局的 uid
        derivation.runId = ++globalState.runId
        // 重點,將當前的derivation分配爲全局的globalState.trackingDerivation,這樣被觀察的 Observable 在其 reportObserved 方法中就能獲取到該 Derivation
        const prevTracking = globalState.trackingDerivation
        globalState.trackingDerivation = derivation
        let result
        // 下面運行存入track的函數,觸發被觀察變量的get方法
        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
    }

能夠看到,重點有兩個,一個是將當前的derivation分配爲全局的globalState.trackingDerivation,一個是下面的更新依賴過程。

  • 接下來,咱們看看觸發了被觀察變量的get方法,會是怎樣的,上面說過,調用get方法會執行reportObserved函數

    export function reportObserved(observable: IObservable): boolean {
        // 拿到剛纔被設置到全局的derivation
        const derivation = globalState.trackingDerivation
        if (derivation !== null) {
            if (derivation.runId !== observable.lastAccessedBy) {
                observable.lastAccessedBy = derivation.runId
                // 這行是重點,將被觀察的變量,放到derivation.newObserving數組中,所以,derivation裏就存放了此次訪問中被觀察的變量
                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
    }
  • 以後是bindDependencies函數的執行。這裏面有兩點,不作代碼解讀了:

    • 一是主要是比較derivation的新舊observing(存放被觀察變量的數組),防止重複記錄,同時去除已過時的被觀察變量
    • 二是,observable(被觀察的變量)的observers(是一個Set結構)更新裏面存放的derivation,即記錄自身被誰觀察了,在後面調用reportChanged時,觸發響應函數

被觀察的變量發生變化時

此時會調用observable的set函數,而後調用reportChanged,最終會調用一個叫作propagateChanged函數。

export function propagateChanged(observable: IObservable) {
    // 已經在運行了,直接返回
    if (observable.lowestObserverState === IDerivationState.STALE) return
    observable.lowestObserverState = IDerivationState.STALE

    // 上面說過,observable(被觀察的變量)的observers存放着derivation
    // 這裏就是執行每一個derivation的onBecomeStale函數
    observable.observers.forEach(d => {
        if (d.dependenciesState === IDerivationState.UP_TO_DATE) {
            if (d.isTracing !== TraceMode.NONE) {
                logTraceInfo(d, observable)
            }
            d.onBecomeStale()
        }
        d.dependenciesState = IDerivationState.STALE
    })
}

onBecomeStale最終會調用derivation裏的schedule函數,裏面作了兩件事:

  • 把自身推動全局的globalState.pendingReactions數組
  • 執行runReactions函數

    • 該函數就核心就作一件事情,遍歷globalState.pendingReactions數組,執行裏面每一個derivation的runReaction函數
    • runReaction最終會調用derivation自身的onInvalidate,即響應函數

至此,整個mobx的數據觀察與響應流程就都一一解釋完整了(autorun,autorunAsync,when等函數都是基於Reaction來實現的,就不做過多解讀了)

Mobx-React源碼簡記

既然mobx都說了,那就把mobx-react也分析一下吧。其實很簡單,只要理解了Reaction與Observable,就很容易明白mobx-react的實現了。

mobx-react的實現主要也是兩點

  • 經過provide和inject,將已經被觀察過的observerableStore集中起來並按需分配到所須要的組件中
  • 被observer的組件,改寫其render函數,使其能夠響應變化

第一點比較簡單,實現一個hoc,把observerableStore添加到context上,而後被inject的組件就能夠拿到所需的observerableStore

咱們重點看下第二點,實現第二點的主要邏輯,在observer.js裏面的makeComponentReactive函數中,看下面簡化版的重點解析

// makeComponentReactive
function makeComponentReactive(render) {
    if (isUsingStaticRendering === true) return render.call(this)
    // 改造後的render函數
    function reactiveRender() {
        // 防止重複執行響應函數,由於componentWillReact有可能有反作用
        isRenderingPending = false
        // render函數執行後返回的jsx
        let rendering = undefined
        // 註冊須要被追蹤的變量
        reaction.track(() => {
            if (isDevtoolsEnabled) {
                this.__$mobRenderStart = Date.now()
            }
            try {
                // _allowStateChanges是安全地執行原來的render函數,假如在action外有更改變量的行爲,會報錯
                // 重點是這個,由於render函數被執行了,因此假如裏面有被observe過的變量,就能被追蹤,更新到依賴該reaction的依賴列表裏面
                rendering = _allowStateChanges(false, baseRender)
            } catch (e) {
                exception = e
            }
            if (isDevtoolsEnabled) {
                this.__$mobRenderEnd = Date.now()
            }
        })
        
        return rendering
    }
    // ....省略一些代碼
    // 新建一個Reaction,註冊響應函數
    const reaction = new Reaction(`${initialName}#${rootNodeID}.render()`, () => {
        if (!isRenderingPending) {
            // 正在執行響應函數
            isRenderingPending = true
            // 這裏就是執行新的componentWillReact生命週期的地方
            if (typeof this.componentWillReact === "function") this.componentWillReact() 
            if (this.__$mobxIsUnmounted !== true) {
                let hasError = true
                try {
                    setHiddenProp(this, isForcingUpdateKey, true)
                    // 也是重點,經過forceUpdate,更新組件
                    if (!this[skipRenderKey]) Component.prototype.forceUpdate.call(this)
                    hasError = false
                } finally {
                    setHiddenProp(this, isForcingUpdateKey, false)
                    if (hasError) reaction.dispose()
                }
            }
        }
    })
    // 改寫原來的render
    reaction.reactComponent = this
    reactiveRender[mobxAdminProperty] = reaction
    this.render = reactiveRender
    return reactiveRender.call(this)
}

能夠見到,經過創建一個Reaction,實現了render函數裏的被觀察的變量收集及響應函數註冊。並且在經過forceUpdate從新更新組件後,render函數會被從新執行一遍,從而實時更新被觀察的變量。總體的實現仍是巧妙的。

除此以外,還有一些生命週期的優化,對props、state也進行監聽等操做,在這裏就不一一解讀了

相關文章
相關標籤/搜索