您好,登錄后才能下訂單哦!
基于Vue3.0發布在GitHub上的第一版源碼(2019.10.05)整理
預備知識
先把Vue3.0跑起來
先把vue-next倉庫的代碼clone下來,安裝依賴然后構建一下,vue的package下的dist目錄下找到構建的腳本,引入腳本即可。
下面一個簡單計數器的DEMO:
<!DOCTYPE html> <html lang="en"> <body> <div id='app'></div> </body> <script src="./dist/vue.global.js"></script> <script> const { createApp, reactive, computed } = Vue; const RootComponent = { template: ` <button @click="increment"> Count is: {{ state.count }} </button> `, setup() { const state = reactive({ count: 0, }) function increment() { state.count++ } return { state, increment } } } createApp().mount(RootComponent, '#app') </script> </html>
template和之前一樣,同樣Vue3也支持手寫render的寫法,template和render同時存在的情況,優先render。
setup選項是新增的主要變動,顧名思義,setup函數會在組件掛載前(beforeCreate和created生命周期之間)運行一次,類似組件初始化的作用,setup需要返回一個對象或者函數。返回對象會被賦值給組件實例的renderContext,在組件的模板作用域可以被訪問到,類似data的返回值。返回函數會被當做是組件的render。具體可以細看文檔。
reactive的作用是將對象包裝成響應式對象,通過Proxy代理后的對象。
上面的計數器的例子,在組件的setup函數中,創建了一個響應式對象state包含一個count屬性。然后創建了一個increment遞增的函數,最后將state和increment返回給作用域,這樣template里的button按鈕就能訪問到increment函數綁定到點擊的回調,count也能顯示在按鈕上。我們點擊按鈕,按鈕上的數值就能跟著遞增。
下面切入正題,我們就來探究下按鈕上count值跟著響應式更新的原理
數據結構
首先列一下主要的一些數據結構,先列在這里,后面提到可以翻回來看看。
ReactiveEffect 一個Function對象,用于執行組件的掛載和更新。
interface ReactiveEffect { (): any isEffect: true active: boolean raw: Function // 具體執行的函數 deps: Array<Dep> computed?: boolean scheduler?: (run: Function) => void onTrack?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void onStop?: () => void }
targetMap 類似 {target -> key -> dep}的一個Map結構,用于緩存所有響應式對象和依賴收集。
export type Dep = Set<ReactiveEffect> export type KeyToDepMap = Map<string | symbol, Dep> export const targetMap: WeakMap<any, KeyToDepMap> = new WeakMap()
Proxy代理攔截
reactive函數執行,會將傳入的target對象通過Proxy包裝,攔截它的get,set等,并將代理的target緩存到targetMap,targetMap.set(target, new Map())。
代理的get的時候會調用一個track函數,而set會調用一個triger函數。分別對應依賴收集和觸發更新。
// Proxy get 簡化 function get(target: any, key: string | symbol, receiver: any) { // 通過key拿到原始值res const res = Reflect.get(target, key, receiver) // 過濾不需要代理的情況 // ... // 依賴收集 track(target, OperationTypes.GET, key) // 如果取到的值是個對象,將對象再代理包裝一下 // Proxy只能代理對象第一層級 return isObject(res) ? reactive(res) : res } // Proxy set 簡化 function set( target: any, key: string | symbol, value: any, receiver: any ): boolean { // 一些不需要代理設置的場景 // ... // 設置原始對象的值 const result = Reflect.set(target, key, value, receiver) // 避免重復trigger的邏輯 // ... // 觸發通知更新 trigger(target, '更新的類型, 新增key或更新key', key) return result }
依賴收集和觸發更新
組件在render階段,視圖會讀取數據對象上的值進行渲染,此時便觸發了Proxy的get,由此觸發對應的track函數,記錄下了對應的ReactiveEffect,也就是常說的依賴收集。
ReactiveEffect其實就可以看作是組件的更新(mount是特殊的update),數據的變更觸發trigger,trigger遍歷調用track收集的對應的數據的ReactiveEffect,也就是對應有關聯的組件的更新。
trigger觸發的組件的更新,在render階段又觸發了新一輪的track依賴收集,更新依賴。
// 簡化的 track function track( target: any, type: OperationTypes, key?: string | symbol ) { // 只有在依賴收集階段才進行依賴收集 // 除了render,其他場景也可能會觸發Proxy的get,但不需要進行依賴收集 // activeReactiveEffectStack棧頂包裝了當前render的組件的mount和update的邏輯 const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1] // 如果effect為空,說明當前不在render階段 if (effect) { // ... // =====>初始化對應{target -> key -> dep}的結構 let depsMap = targetMap.get(target) if (depsMap === void 0) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key as string | symbol) if (!dep) { depsMap.set(key as string | symbol, (dep = new Set())) } // <=====初始化對應{target -> key -> dep}的結構 // 依賴列表里如果沒有,add if (!dep.has(effect)) { // 這里將effect作為依賴,緩存到依賴列表 dep.add(effect) effect.deps.push(dep) } } } // 簡化的trigger function trigger( target: any, type: OperationTypes, key?: string | symbol, extraInfo?: any ) { // 獲取對應target在track過程中緩存的依賴 const depsMap = targetMap.get(target) const effects: Set<ReactiveEffect> = new Set() // 省略分類邏輯 depsMap.forEach(dep => { // 將effect分類過濾添加到effects }) const run = (effect: ReactiveEffect) => { // 有個異步調度的過程,nextTick scheduleRun(effect, target, type, key, extraInfo) } effects.forEach(run) }
大致流程:
總結
現在的代碼只有新特性的實現,而且ES6+TS的組合可讀性大大提高,編輯器支持也很好,所以相對會好讀很多。這里只是簡單的理了一下vue 3.0 reactive的整體流程,細節還有很多地方值得學習,繼續加油。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。