Vue3.0數(shù)據(jù)響應(yīng)式原理詳解
基于Vue3.0發(fā)布在GitHub上的第一版源碼(2019.10.05)整理
預(yù)備知識(shí)
- ES6 Proxy,整個(gè)響應(yīng)式系統(tǒng)的基礎(chǔ)。
- 新的composition-API的基本使用,目前還沒(méi)有中文文檔,可以先通過(guò)這個(gè)倉(cāng)庫(kù)(composition-api-rfc)了解,里面也有對(duì)應(yīng)的在線文檔。
先把Vue3.0跑起來(lái)
先把vue-next倉(cāng)庫(kù)的代碼clone下來(lái),安裝依賴然后構(gòu)建一下,vue的package下的dist目錄下找到構(gòu)建的腳本,引入腳本即可。
下面一個(gè)簡(jiǎn)單計(jì)數(shù)器的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也支持手寫(xiě)render的寫(xiě)法,template和render同時(shí)存在的情況,優(yōu)先render。
setup選項(xiàng)是新增的主要變動(dòng),顧名思義,setup函數(shù)會(huì)在組件掛載前(beforeCreate和created生命周期之間)運(yùn)行一次,類似組件初始化的作用,setup需要返回一個(gè)對(duì)象或者函數(shù)。返回對(duì)象會(huì)被賦值給組件實(shí)例的renderContext,在組件的模板作用域可以被訪問(wèn)到,類似data的返回值。返回函數(shù)會(huì)被當(dāng)做是組件的render。具體可以細(xì)看文檔。
reactive的作用是將對(duì)象包裝成響應(yīng)式對(duì)象,通過(guò)Proxy代理后的對(duì)象。
上面的計(jì)數(shù)器的例子,在組件的setup函數(shù)中,創(chuàng)建了一個(gè)響應(yīng)式對(duì)象state包含一個(gè)count屬性。然后創(chuàng)建了一個(gè)increment遞增的函數(shù),最后將state和increment返回給作用域,這樣template里的button按鈕就能訪問(wèn)到increment函數(shù)綁定到點(diǎn)擊的回調(diào),count也能顯示在按鈕上。我們點(diǎn)擊按鈕,按鈕上的數(shù)值就能跟著遞增。
下面切入正題,我們就來(lái)探究下按鈕上count值跟著響應(yīng)式更新的原理
數(shù)據(jù)結(jié)構(gòu)
首先列一下主要的一些數(shù)據(jù)結(jié)構(gòu),先列在這里,后面提到可以翻回來(lái)看看。
ReactiveEffect 一個(gè)Function對(duì)象,用于執(zhí)行組件的掛載和更新。
interface ReactiveEffect { (): any isEffect: true active: boolean raw: Function // 具體執(zhí)行的函數(shù) deps: Array<Dep> computed?: boolean scheduler?: (run: Function) => void onTrack?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void onStop?: () => void }
targetMap 類似 {target -> key -> dep}的一個(gè)Map結(jié)構(gòu),用于緩存所有響應(yīng)式對(duì)象和依賴收集。
export type Dep = Set<ReactiveEffect> export type KeyToDepMap = Map<string | symbol, Dep> export const targetMap: WeakMap<any, KeyToDepMap> = new WeakMap()
Proxy代理攔截
reactive函數(shù)執(zhí)行,會(huì)將傳入的target對(duì)象通過(guò)Proxy包裝,攔截它的get,set等,并將代理的target緩存到targetMap,targetMap.set(target, new Map())。
代理的get的時(shí)候會(huì)調(diào)用一個(gè)track函數(shù),而set會(huì)調(diào)用一個(gè)triger函數(shù)。分別對(duì)應(yīng)依賴收集和觸發(fā)更新。
// Proxy get 簡(jiǎn)化 function get(target: any, key: string | symbol, receiver: any) { // 通過(guò)key拿到原始值res const res = Reflect.get(target, key, receiver) // 過(guò)濾不需要代理的情況 // ... // 依賴收集 track(target, OperationTypes.GET, key) // 如果取到的值是個(gè)對(duì)象,將對(duì)象再代理包裝一下 // Proxy只能代理對(duì)象第一層級(jí) return isObject(res) ? reactive(res) : res } // Proxy set 簡(jiǎn)化 function set( target: any, key: string | symbol, value: any, receiver: any ): boolean { // 一些不需要代理設(shè)置的場(chǎng)景 // ... // 設(shè)置原始對(duì)象的值 const result = Reflect.set(target, key, value, receiver) // 避免重復(fù)trigger的邏輯 // ... // 觸發(fā)通知更新 trigger(target, '更新的類型, 新增key或更新key', key) return result }
依賴收集和觸發(fā)更新
組件在render階段,視圖會(huì)讀取數(shù)據(jù)對(duì)象上的值進(jìn)行渲染,此時(shí)便觸發(fā)了Proxy的get,由此觸發(fā)對(duì)應(yīng)的track函數(shù),記錄下了對(duì)應(yīng)的ReactiveEffect,也就是常說(shuō)的依賴收集。
ReactiveEffect其實(shí)就可以看作是組件的更新(mount是特殊的update),數(shù)據(jù)的變更觸發(fā)trigger,trigger遍歷調(diào)用track收集的對(duì)應(yīng)的數(shù)據(jù)的ReactiveEffect,也就是對(duì)應(yīng)有關(guān)聯(lián)的組件的更新。
trigger觸發(fā)的組件的更新,在render階段又觸發(fā)了新一輪的track依賴收集,更新依賴。
// 簡(jiǎn)化的 track function track( target: any, type: OperationTypes, key?: string | symbol ) { // 只有在依賴收集階段才進(jìn)行依賴收集 // 除了render,其他場(chǎng)景也可能會(huì)觸發(fā)Proxy的get,但不需要進(jìn)行依賴收集 // activeReactiveEffectStack棧頂包裝了當(dāng)前render的組件的mount和update的邏輯 const effect = activeReactiveEffectStack[activeReactiveEffectStack.length - 1] // 如果effect為空,說(shuō)明當(dāng)前不在render階段 if (effect) { // ... // =====>初始化對(duì)應(yīng){target -> key -> dep}的結(jié)構(gòu) 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())) } // <=====初始化對(duì)應(yīng){target -> key -> dep}的結(jié)構(gòu) // 依賴列表里如果沒(méi)有,add if (!dep.has(effect)) { // 這里將effect作為依賴,緩存到依賴列表 dep.add(effect) effect.deps.push(dep) } } } // 簡(jiǎn)化的trigger function trigger( target: any, type: OperationTypes, key?: string | symbol, extraInfo?: any ) { // 獲取對(duì)應(yīng)target在track過(guò)程中緩存的依賴 const depsMap = targetMap.get(target) const effects: Set<ReactiveEffect> = new Set() // 省略分類邏輯 depsMap.forEach(dep => { // 將effect分類過(guò)濾添加到effects }) const run = (effect: ReactiveEffect) => { // 有個(gè)異步調(diào)度的過(guò)程,nextTick scheduleRun(effect, target, type, key, extraInfo) } effects.forEach(run) }
大致流程:
總結(jié)
現(xiàn)在的代碼只有新特性的實(shí)現(xiàn),而且ES6+TS的組合可讀性大大提高,編輯器支持也很好,所以相對(duì)會(huì)好讀很多。這里只是簡(jiǎn)單的理了一下vue 3.0 reactive的整體流程,細(xì)節(jié)還有很多地方值得學(xué)習(xí),繼續(xù)加油。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Vue首頁(yè)界面加載優(yōu)化實(shí)現(xiàn)方法詳解
如果你也遇到在 vue 應(yīng)用中,首頁(yè)加載資源過(guò)多,導(dǎo)致加載緩慢的問(wèn)題,下面的方法也許能幫到你,主要的處理思想是:讓首頁(yè)多余的資源能在需要它的時(shí)候再加載2022-09-09vue+webpack dev本地調(diào)試全局樣式引用失效的解決方案
今天小編就為大家分享一篇vue+webpack dev本地調(diào)試全局樣式引用失效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11解決vue2.0路由跳轉(zhuǎn)未匹配相應(yīng)用路由避免出現(xiàn)空白頁(yè)面的問(wèn)題
今天小編就為大家分享一篇解決vue2.0路由跳轉(zhuǎn)未匹配相應(yīng)用路由避免出現(xiàn)空白頁(yè)面的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08vue前端自適應(yīng)布局實(shí)現(xiàn)教程(一步到位所有自適應(yīng))
?自適應(yīng)布局是一種根據(jù)不同的設(shè)備屏幕分辨率進(jìn)行布局的方式,它為不同的屏幕分辨率定義了不同的布局,下面這篇文章主要給大家介紹了關(guān)于vue前端自適應(yīng)布局實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2024-08-08vue3 reactive 請(qǐng)求接口數(shù)據(jù)賦值后拿不到的問(wèn)題及解決方案
這篇文章主要介紹了vue3 reactive 請(qǐng)求接口數(shù)據(jù)賦值后拿不到的問(wèn)題及解決方案,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2024-04-04Vue 實(shí)現(xiàn)CLI 3.0 + momentjs + lodash打包時(shí)優(yōu)化
今天小編就為大家分享一篇Vue 實(shí)現(xiàn)CLI 3.0 + momentjs + lodash打包時(shí)優(yōu)化,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11解決vue init webpack 下載依賴卡住不動(dòng)的問(wèn)題
這篇文章主要介紹了解決vue init webpack 下載依賴卡住不動(dòng)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11