詳解vue3?響應(yīng)式的實現(xiàn)原理
核心設(shè)計思想
除了組件化,Vue.js 另一個核心設(shè)計思想就是響應(yīng)式。它的本質(zhì)是當(dāng)數(shù)據(jù)變化后會自動執(zhí)行某個函數(shù),映射到組件的實現(xiàn)就是,當(dāng)數(shù)據(jù)變化后,會自動觸發(fā)組件的重新渲染。響應(yīng)式是 Vue.js 組件化更新渲染的一個核心機制。
Vue.js 2.x 響應(yīng)式
我們先來回顧一下 Vue.js 2.x 響應(yīng)式實現(xiàn)的部分: 它在內(nèi)部通過 Object.defineProperty API 劫持數(shù)據(jù)的變化,在數(shù)據(jù)被訪問的時候收集依賴,然后在數(shù)據(jù)被修改的時候通知依賴更新。
在 Vue.js 2.x 中,Watcher 就是依賴,有專門針對組件渲染的 render watcher。這里有兩個流程,首先是依賴收集流程,組件在 render 的時候會訪問模板中的數(shù)據(jù),觸發(fā) getter 把 render watcher 作為依賴收集,并和數(shù)據(jù)建立聯(lián)系;然后是派發(fā)通知流程,當(dāng)我對這些數(shù)據(jù)修改的時候,會觸發(fā) setter,通知 render watcher 更新,進而觸發(fā)了組件的重新渲染。
Object.defineProperty API 的一些缺點:
不能監(jiān)聽對象屬性新增和刪除; 初始化階段遞歸執(zhí)行 Object.defineProperty 帶來的性能負擔(dān)。
在 Vue.js 2.x 中,data 中定義的數(shù)據(jù),Vue.js 內(nèi)部在組件初始化的過程中會把它變成響應(yīng)式,這是一個相對黑盒的過程,用戶通常不會感知到。
Vue.js 3.x 響應(yīng)式
Vue.js 3.0 為了解決 Object.defineProperty 的這些缺陷,使用 Proxy API 重寫了響應(yīng)式部分,并獨立維護和發(fā)布整個 reactivity 庫。
也就是在 Vue.js 3.0 中,是用 reactive 這個有魔力的函數(shù),把數(shù)據(jù)變成了響應(yīng)式。
reactive 內(nèi)部通過 createReactiveObject 函數(shù)把 target 變成了一個響應(yīng)式對象,這個函數(shù)主要做了以下幾件事情:
1、函數(shù)首先判斷 target 是不是數(shù)組或者對象類型,如果不是則直接返回。所以**原始數(shù)據(jù) target 必須是對象或者數(shù)組**。 2、如果對一個已經(jīng)是響應(yīng)式的對象再次執(zhí)行 reactive,還應(yīng)該返回這個響應(yīng)式對象。 3、如果對同一個原始數(shù)據(jù)多次執(zhí)行 reactive ,那么會返回相同的響應(yīng)式對象。 4、使用 canObserve 函數(shù)對 target 對象做一進步限制。 5、通過 Proxy API 劫持 target 對象,把它變成響應(yīng)式。 6、給原始數(shù)據(jù)打個標(biāo)識。
響應(yīng)式的實現(xiàn)方式無非就是劫持數(shù)據(jù),Vue.js 3.0 的 reactive API 就是通過 Proxy 劫持數(shù)據(jù),而且由于 Proxy 劫持的是整個對象,所以我們可以檢測到任何對對象的修改,彌補了 Object.defineProperty API 的不足。
依賴收集:get 函數(shù)
依賴收集發(fā)生在數(shù)據(jù)訪問的階段
get 函數(shù)主要做了四件事情:
1、對特殊的 key 做了代理 2、通過 Reflect.get 方法求值 3、執(zhí)行 track 函數(shù)收集依賴(最核心) 4、對計算的值 res 進行判斷,如果它也是數(shù)組或?qū)ο?,則遞歸執(zhí)行 reactive 把 res 變成響應(yīng)式對象。
Object.defineProperty 是在初始化階段,即定義劫持對象的時候就已經(jīng)遞歸執(zhí)行了,而 Proxy 是在對象屬性被訪問的時候才遞歸執(zhí)行下一步 reactive,這其實是一種延時定義子對象響應(yīng)式的實現(xiàn),在性能上會有較大的提升
收集的依賴就是數(shù)據(jù)變化后執(zhí)行的副作用函數(shù)。
每次 track ,就是把當(dāng)前激活的副作用函數(shù) activeEffect 作為依賴,然后收集到 target 相關(guān)的 depsMap 對應(yīng) key 下的依賴集合 dep 中。
派發(fā)通知:set 函數(shù)
派發(fā)通知發(fā)生在數(shù)據(jù)更新的階段
set 函數(shù)主要就做兩件事情:
1、通過 Reflect.set 求值 2、通過 trigger 函數(shù)派發(fā)通知(最核心),并依據(jù) key 是否存在于 target 上來確定通知類型,即新增還是修改。
trigger 函數(shù)主要做了四件事情:
1、通過 targetMap 拿到 target 對應(yīng)的依賴集合 depsMap; 2、創(chuàng)建運行的 effects 集合; 3、根據(jù) key 從 depsMap 中找到對應(yīng)的 effect 添加到 effects 集合; 4、遍歷 effects 執(zhí)行相關(guān)的副作用函數(shù)。
每次 trigger 函數(shù)就是根據(jù) target 和 key ,從 targetMap 中找到相關(guān)的所有副作用函數(shù)遍歷執(zhí)行一遍。
依賴收集和派發(fā)通知的過程中都提到副作用函數(shù),依賴收集過程中我們把 activeEffect(當(dāng)前激活副作用函數(shù))作為依賴收集。
總結(jié)
其實 Vue.js 3.0 在響應(yīng)式的實現(xiàn)思路和 Vue.js 2.x 差別并不大,主要就是 劫持數(shù)據(jù)的方式改成用 Proxy 實現(xiàn) , 以及收集的依賴由 watcher 實例變成了組件副作用渲染函數(shù) 。
源碼參考
由于源碼太多,本文就不展示,可直接去: github.com/vuejs/core
packages/reactivity/src/baseHandlers.ts packages/reactivity/src/effect.ts packages/reactivity/src/reactive.ts packages/reactivity/src/baseHandlers.ts packages/reactivity/src/ref.ts
到此這篇關(guān)于vue3 響應(yīng)式的實現(xiàn)原理的文章就介紹到這了,更多相關(guān)vue3 響應(yīng)式原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!