摘要:源碼簡記整體會寫得比較亂,同時也比較簡單,和讀書筆記差不多,基本是邊讀邊寫。見諒主要三大部分的原子類,能夠被觀察和通知變化,繼承于。同時里面有幾個比較重要的屬性與方法。
Mobx 源碼簡記
整體會寫得比較亂,同時也比較簡單,和讀書筆記差不多,基本是邊讀邊寫。見諒~
主要三大部分Atom、Observable、Derivation
AtomMobx的原子類,能夠被觀察和通知變化,observableValue繼承于Atom。observableValue ---> Atom
同時里面有幾個比較重要的屬性與方法。
屬性
observers,用于存放這個被原子類被誰觀察了,是一個set結構
diffValue,后續更新依賴的時候要用這個來判斷
方法
reportObserved,調用全局的reportObserved函數,通知自身被觀察了
reportChanged,調用全局的propagateChanged函數,通知自身發生變化了
ObservableObservable是一個工廠函數,讓數據變得可觀察。這個東西需要和上述的Atom建立聯系,即將具體的值與Atom聯系起來。從而打通自身能夠被觀察,同時能通知變化的整個流程。
三種可被觀察的數據類型:對象,數組,Map,下面簡單介紹如何實現。假如都不是,就會提示用戶調用observable.box,使其擁有get,set方法,即上述說的observableValue數據類型。
部分代碼如下:
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三者的實現,下面是討論關于對象的實現方式
對象(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對象的get和set函數,在有新屬性訪問時或改變值時會調用get和set函數
訪問新屬性時,get函數會讀取adm[$mobx],如果沒有,會通過has方法,建立一個**observableValue**,并放到adm[$mobx].pendingKeys中(還不知道有什么用)
設置新屬性時,會新建一個observableValue存放進去adm[$mobx].values中,同時,通過Object.defineProperty設置了該屬性的get和set屬性
重點:(observableValue簡稱為oV,Object.defineProperty簡稱為Od)
上面說的所有通過Od定義后的set會調用已存放的oV的set,get會調用已存放的oV的get
第一點說過oV繼承于Atom,所以oV的set會調用reportChanged,oV的get會調用reportObserved
這樣子,整個對象屬性的監聽流程就建立起來了
ReactionReaction(反應)是一類特殊的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(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也進行監聽等操作,在這里就不一一解讀了
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/102264.html
摘要:隨后,執行官給出一張當張三存款發生變化之時,此機構的運作時序圖的確,小機構靠人力運作,大機構才靠制度運轉。第一條語句創建觀察員第一條語句張三我們調用的時候,就創建了對象,對象的所有屬性都將被拷貝至一個克隆對象并將克隆對象轉變成可觀察的。 ================前言=================== 初衷:網上已有很多關于 MobX 源碼解讀的文章,但大多閱讀成本甚高。...
摘要:對于直接量和局部變量的訪問性能差異微不足道,性能消耗代價高一些的是全局變量數組項對象成員。當一個函數被創建后,作用域鏈中被放入可訪問的對象。同樣會改變作用域鏈,帶來性能問題。 早前閱讀高性能JavaScript一書所做筆記。 一、Loading and Execution 加載和運行 從加載和運行角度優化,源于JavaScript運行會阻塞UI更新,JavaScript腳本的下載、解析...
摘要:對于直接量和局部變量的訪問性能差異微不足道,性能消耗代價高一些的是全局變量數組項對象成員。當一個函數被創建后,作用域鏈中被放入可訪問的對象。同樣會改變作用域鏈,帶來性能問題。 早前閱讀高性能JavaScript一書所做筆記。 一、Loading and Execution 加載和運行 從加載和運行角度優化,源于JavaScript運行會阻塞UI更新,JavaScript腳本的下載、解析...
摘要:前言初衷以系列故事的方式展現源碼邏輯,盡可能以易懂的方式講解源碼本系列文章用故事解讀源碼一用故事解讀源碼二用故事解讀源碼三用故事解讀源碼四裝飾器和用故事解讀源碼五文章編排每篇文章分成兩大段,第一大段以簡單的偵探系列故事的形式講解所涉及人物場 ================前言=================== 初衷:以系列故事的方式展現 MobX 源碼邏輯,盡可能以易懂的方式...
摘要:所以這是一篇插隊的文章,用于去理解中的裝飾器和概念。因此,該的作用就是根據入參返回具體的描述符。其次局部來看,裝飾器具體應用表達式是,其函數簽名和是一模一樣。等裝飾器語法,是和直接使用是等效等價的。 ================前言=================== 初衷:以系列故事的方式展現 MobX 源碼邏輯,盡可能以易懂的方式講解源碼; 本系列文章: 《【用故事解...
閱讀 2406·2021-11-18 10:02
閱讀 1922·2021-10-13 09:40
閱讀 2999·2021-09-07 10:07
閱讀 2106·2021-09-04 16:48
閱讀 1005·2019-08-30 13:18
閱讀 2452·2019-08-29 14:03
閱讀 2921·2019-08-29 12:54
閱讀 3155·2019-08-26 11:41