溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

怎么理解Vue 3.0的響應(yīng)式系統(tǒng)

發(fā)布時(shí)間:2021-11-05 17:07:16 來源:億速云 閱讀:148 作者:iii 欄目:web開發(fā)

這篇文章主要介紹“怎么理解Vue 3.0的響應(yīng)式系統(tǒng)”,在日常操作中,相信很多人在怎么理解Vue 3.0的響應(yīng)式系統(tǒng)問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么理解Vue 3.0的響應(yīng)式系統(tǒng)”的疑惑有所幫助!接下來,請跟著小編一起來學(xué)習(xí)吧!

一個(gè)基本的例子

Vue 3.0 的響應(yīng)式系統(tǒng)是獨(dú)立的模塊,可以完全脫離 Vue 而使用,所以我們在 clone 了源碼下來以后,可以直接在 packages/reactivity 模塊下調(diào)試。

  1.  在項(xiàng)目根目錄運(yùn)行 yarn dev reactivity,然后進(jìn)入 packages/reactivity 目錄找到產(chǎn)出的 dist/reactivity.global.js 文件。

  2.  新建一個(gè) index.html,寫入如下代碼:   

<script src="./dist/reactivity.global.js"></script>     <script>     const { reactive, effect } = VueObserver     const origin = {       count: 0     }     const state = reactive(origin)     const fn = () => {       const count = state.count       console.log(`set count to ${count}`)     }     effect(fn)     </script>

    3.  在瀏覽器打開該文件,于控制臺執(zhí)行 state.count++,便可看到輸出 set count to 1。

在上述的例子中,我們使用 reactive() 函數(shù)把 origin 對象轉(zhuǎn)化成了 Proxy 對象 state;使用 effect() 函數(shù)把 fn() 作為響應(yīng)式回調(diào)。當(dāng) state.count 發(fā)生變化時(shí),便觸發(fā)了 fn()。接下來我們將以這個(gè)例子結(jié)合上文的流程圖,來講解這套響應(yīng)式系統(tǒng)是怎么運(yùn)行的。

初始化階段

怎么理解Vue 3.0的響應(yīng)式系統(tǒng)

在初始化階段,主要做了兩件事。

  1.  把 origin 對象轉(zhuǎn)化成響應(yīng)式的 Proxy 對象 state。

  2.  把函數(shù) fn() 作為一個(gè)響應(yīng)式的 effect 函數(shù)。

首先我們來分析第一件事。

大家都知道,Vue 3.0 使用了 Proxy 來代替之前的 Object.defineProperty(),改寫了對象的 getter/setter,完成依賴收集和響應(yīng)觸發(fā)。但是在這一階段中,我們暫時(shí)先不管它是如何改寫對象的 getter/setter 的,這個(gè)在后續(xù)的”依賴收集階段“會詳細(xì)說明。為了簡單起見,我們可以把這部分的內(nèi)容濃縮成一個(gè)只有兩行代碼的 reactive() 函數(shù):

export function reactive(target) {    const observed = new Proxy(target, handler)    return observed  }

完整代碼在 reactive.js。這里的 handler 就是改造 getter/setter 的關(guān)鍵,我們放到后文講解。

接下來我們分析第二件事。

當(dāng)一個(gè)普通的函數(shù) fn() 被 effect() 包裹之后,就會變成一個(gè)響應(yīng)式的 effect 函數(shù),而 fn() 也會被立即執(zhí)行一次。

由于在 fn() 里面有引用到 Proxy 對象的屬性,所以這一步會觸發(fā)對象的 getter,從而啟動依賴收集。

除此之外,這個(gè) effect 函數(shù)也會被壓入一個(gè)名為”activeReactiveEffectStack“(此處為 effectStack)的棧中,供后續(xù)依賴收集的時(shí)候使用。

來看看代碼(完成代碼請看 effect.js):

export function effect (fn) {    // 構(gòu)造一個(gè) effect    const effect = function effect(...args) {      return run(effect, fn, args)    }    // 立即執(zhí)行一次    effect()    return effect  }  export function run(effect, fn, args) {    if (effectStack.indexOf(effect) === -1) {      try {        // 往池子里放入當(dāng)前 effect        effectStack.push(effect)        // 立即執(zhí)行一遍 fn()        // fn() 執(zhí)行過程會完成依賴收集,會用到 effect        return fn(...args)      } finally {        // 完成依賴收集后從池子中扔掉這個(gè) effect        effectStack.pop()      }    }  }

至此,初始化階段已經(jīng)完成。接下來就是整個(gè)系統(tǒng)最關(guān)鍵的一步&mdash;&mdash;依賴收集階段。

依賴收集階段

怎么理解Vue 3.0的響應(yīng)式系統(tǒng)

這個(gè)階段的觸發(fā)時(shí)機(jī),就是在 effect 被立即執(zhí)行,其內(nèi)部的 fn() 觸發(fā)了 Proxy 對象的 getter 的時(shí)候。簡單來說,只要執(zhí)行到類似 state.count 的語句,就會觸發(fā) state 的 getter。

依賴收集階段最重要的目的,就是建立一份”依賴收集表“,也就是圖示的”targetMap"。targetMap 是一個(gè) WeakMap,其 key 值是當(dāng)前的 Proxy 對象 state代理前的對象origin,而 value 則是該對象所對應(yīng)的 depsMap。

depsMap 是一個(gè) Map,key 值為觸發(fā) getter 時(shí)的屬性值(此處為 count),而 value 則是觸發(fā)過該屬性值所對應(yīng)的各個(gè) effect。

還是有點(diǎn)繞?那么我們再舉個(gè)例子。假設(shè)有個(gè) Proxy 對象和 effect 如下:

const state = reactive({    count: 0,    age: 18  })  const effecteffect1 = effect(() => {    console.log('effect1: ' + state.count)  })  const effecteffect2 = effect(() => {    console.log('effect2: ' + state.age)  })  const effecteffect3 = effect(() => {    console.log('effect3: ' + state.count, state.age)  })

那么這里的 targetMap 應(yīng)該為這個(gè)樣子:

怎么理解Vue 3.0的響應(yīng)式系統(tǒng)

這樣,{ target -> key -> dep } 的對應(yīng)關(guān)系就建立起來了,依賴收集也就完成了。代碼如下:

export function track (target, operationType, key) {    const effect = effectStack[effectStack.length - 1]    if (effect) {      let depsMap = targetMap.get(target)      if (depsMap === void 0) {        targetMap.set(target, (depsMap = new Map()))      }      let dep = depsMap.get(key)      if (dep === void 0) {        depsMap.set(key, (dep = new Set()))      }      if (!dep.has(effect)) {        dep.add(effect)      }    }  }

弄明白依賴收集表 targetMap 是非常重要的,因?yàn)檫@是整個(gè)響應(yīng)式系統(tǒng)核心中的核心。

響應(yīng)階段

回顧上一章節(jié)的例子,我們得到了一個(gè) { count: 0, age: 18 } 的 Proxy,并構(gòu)造了三個(gè) effect。在控制臺上看看效果:

怎么理解Vue 3.0的響應(yīng)式系統(tǒng)

效果符合預(yù)期,那么它是怎么實(shí)現(xiàn)的呢?首先來看看這個(gè)階段的原理圖:

怎么理解Vue 3.0的響應(yīng)式系統(tǒng)

當(dāng)修改對象的某個(gè)屬性值的時(shí)候,會觸發(fā)對應(yīng)的 setter。

setter 里面的 trigger() 函數(shù)會從依賴收集表里找到當(dāng)前屬性對應(yīng)的各個(gè) dep,然后把它們推入到 effects 和 computedEffects(計(jì)算屬性) 隊(duì)列中,最后通過 scheduleRun() 挨個(gè)執(zhí)行里面的 effect。

由于已經(jīng)建立了依賴收集表,所以要找到屬性所對應(yīng)的 dep 也就輕而易舉了,可以看看具體的代碼實(shí)現(xiàn):

export function trigger (target, operationType, key) {    // 取得對應(yīng)的 depsMap    const depsMap = targetMap.get(target)    if (depsMap === void 0) {      return    }    // 取得對應(yīng)的各個(gè) dep    const effects = new Set()    if (key !== void 0) {      const dep = depsMap.get(key)      dep && dep.forEach(effect => {        effects.add(effect)      })    }    // 簡化版 scheduleRun,挨個(gè)執(zhí)行 effect    effects.forEach(effect => {      effect()    })  }

這里的代碼沒有處理諸如數(shù)組的 length 被修改的一些特殊情況,感興趣的讀者可以查看 vue-next 對應(yīng)的源碼,或者這篇文章,看看這些情況都是怎么處理的。

至此,響應(yīng)式階段完成。

到此,關(guān)于“怎么理解Vue 3.0的響應(yīng)式系統(tǒng)”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識,請繼續(xù)關(guān)注億速云網(wǎng)站,小編會繼續(xù)努力為大家?guī)砀鄬?shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

vue
AI