vue3.0響應(yīng)式函數(shù)原理詳細
前言:
Vue3重寫了響應(yīng)式系統(tǒng),和Vue2相比底層采用Proxy
對象實現(xiàn),在初始化的時候不需要遍歷所有的屬性再把屬性通過defineProperty轉(zhuǎn)換成get和set。另外如果有多層屬性嵌套的話只有訪問某個屬性的時候才會遞歸處理下一級的屬性所以Vue3中響應(yīng)式系統(tǒng)的性能要比Vue2好。
接下來我們自己實現(xiàn)Vue3響應(yīng)式系統(tǒng)的核心函數(shù)(reactive/ref/toRefs/computed/effect/track/trigger)
來學習一下響應(yīng)式原理。
首先我們使用Proxy來實現(xiàn)響應(yīng)式中的第一個函數(shù)reactive
。
1.reactive
reactive
接收一個參數(shù),首先要判斷這個參數(shù)是否是一個對象,如果不是直接返回,reactive
只能將對象轉(zhuǎn)換成響應(yīng)式對象,這是和ref不同的地方。
接著會創(chuàng)建攔截器對象handler, 其中抱哈get
,set
,deleteProperty
等攔截方法,最后創(chuàng)建并返回Proxy對象。
// 判斷是否是一個對象 const isObject = val => val !== null && typeof val === 'object' // 如果是對象則調(diào)用reactive const convert= target => isObject(target) ? reactive(target) : target // 判斷對象是否存在key屬性 const haOwnProperty = Object.prototype.hasOwnProperty const hasOwn = (target, key) => haOwnProperty.call(target, key) export function reactive (target) { ? ? if (!isObject(target)) { ? ? ? ? // 如果不是對象直接返回 ? ? ? ? return target ? ? } ? ? const handler = { ? ? ? ? get (target, key, receiver) { ? ? ? ? ? ? // 收集依賴 ? ? ? ? ? ? const result = Reflect.get(target, key, receiver) ? ? ? ? ? ? // 如果屬性是對象則需要遞歸處理 ? ? ? ? ? ? return convert(result) ? ? ? ? }, ? ? ? ? set (target, key, value, receiver) { ? ? ? ? ? ? const oldValue = Reflect.get(target, key, receiver) ? ? ? ? ? ? let result = true; ? ? ? ? ? ? // 需要判斷當前傳入的新值和oldValue是否相等,如果不相等再去覆蓋舊值,并且觸發(fā)更新 ? ? ? ? ? ? if (oldValue !== value) { ? ? ? ? ? ? ? ? result = Reflect.set(target, key, value, receiver) ? ? ? ? ? ? ? ? // 觸發(fā)更新... ? ? ? ? ? ? } ? ? ? ? ? ? // set方法需要返回布爾值 ? ? ? ? ? ? return result; ? ? ? ? }, ? ? ? ? deleteProperty (target, key) { ? ? ? ? ? ? // 首先要判斷當前target中是否有自己的key屬性 ? ? ? ? ? ? // 如果存在key屬性,并且刪除要觸發(fā)更新 ? ? ? ? ? ? const hasKey = hasOwn(target, key) ? ? ? ? ? ? const result = Reflect.deleteProperty(target, key) ? ? ? ? ? ? if (hasKey && result) { ? ? ? ? ? ? ? ? // 觸發(fā)更新... ? ? ? ? ? ? } ? ? ? ? ? ? return result; ? ? ? ? } ? ? } ? ? return new Proxy(target, handler) }
至此reactive
函數(shù)就寫完了,接著我們來編寫一下收集依賴的過程。
在依賴收集的過程會創(chuàng)建三個集合,分別是targetMap
,depsMap
以及dep
。
其中targetMap
是用來記錄目標對象和字典他使用的是weakMap
,key是目標對象,targetMap的值是depsMap, 類型是Map,這里面的key是目標對象的屬性名稱,值是一個Set集合,集合中存儲的元素是Effect函數(shù)。因為可以多次調(diào)用同一個Effect在Effect訪問同一個屬性,這個時候這個屬性會收集多次依賴對應(yīng)多個Effect函數(shù)。
一個屬性可以對應(yīng)多個Effect函數(shù),觸發(fā)更新的時候可以通過屬性找到對應(yīng)的Effect函數(shù),進行執(zhí)行。
我們這里分別來實現(xiàn)一下effect
和track
兩個函數(shù)。
effect函數(shù)接收一個函數(shù)作為參數(shù),我們首先在外面定一個變量存儲callback, 這樣track函數(shù)就可以訪問到callback了。
let activeEffect = null; export function effect (callback) { ? ? activeEffect = callback; ? ? // 訪問響應(yīng)式對象屬性,收集依賴 ? ? callback(); ? ? // 依賴收集結(jié)束要置null ? ? activeEffect = null; }
track
函數(shù)接收兩個參數(shù)目標對象和屬性, 他的內(nèi)部要將target
存儲到targetMap
中。需要先定義一個這樣的Map。
let targetMap = new WeakMap() export function track (target, key) { ? ? // 判斷activeEffect是否存在 ? ? if (!activeEffect) { ? ? ? ? return; ? ? } ? ? // depsMap存儲對象和effect的對應(yīng)關(guān)系 ? ? let depsMap = targetMap.get(target) ? ? // 如果不存在則創(chuàng)建一個map存儲到targetMap中 ? ? if (!depsMap) { ? ? ? ? targetMap.set(target, (depsMap = new Map())) ? ? } ? ? // 根據(jù)屬性查找對應(yīng)的dep對象 ? ? let dep = depsMap.get(key) ? ? // dep是一個集合,用于存儲屬性所對應(yīng)的effect函數(shù) ? ? if (!dep) { ? ? ? ? // 如果不存在,則創(chuàng)建一個新的集合添加到depsMap中 ? ? ? ? depsMap.set(key, (dep = new Set())) ? ? } ? ? dep.add(activeEffect) }
track
是依賴收集的函數(shù)。需要在reactive
函數(shù)的get方法中調(diào)用。
get (target, key, receiver) { ? ? // 收集依賴 ? ? track(target, key) ? ? const result = Reflect.get(target, key, receiver) ? ? // 如果屬性是對象則需要遞歸處理 ? ? return convert(result) },
這樣整個依賴收集就完成了。接著就要實現(xiàn)觸發(fā)更新,對應(yīng)的函數(shù)是trigger,這個過程和track的過程正好相反。
trigger
函數(shù)接收兩個參數(shù),分別是target和key。
export function trigger (target, key) { ? ? const depsMap = targetMap.get(target) ? ? // 如果沒有找到直接返回 ? ? if (!depsMap) { ? ? ? ? return; ? ? } ? ? const dep = depsMap.get(key) ? ? if (dep) { ? ? ? ? dep.forEach(effect => { ? ? ? ? ? ? effect() ? ? ? ? }) ? ? } }
trigger函數(shù)要在reactive函數(shù)中的set
和deleteProperty
中觸發(fā)。
set (target, key, value, receiver) { ? ? const oldValue = Reflect.get(target, key, receiver) ? ? let result = true; ? ? // 需要判斷當前傳入的新值和oldValue是否相等,如果不相等再去覆蓋舊值,并且觸發(fā)更新 ? ? if (oldValue !== value) { ? ? ? ? result = Reflect.set(target, key, value, receiver) ? ? ? ? // 觸發(fā)更新... ? ? ? ? trigger(target, key) ? ? } ? ? // set方法需要返回布爾值 ? ? return result; }, deleteProperty (target, key) { ? ? // 首先要判斷當前target中是否有自己的key屬性 ? ? // 如果存在key屬性,并且刪除要觸發(fā)更新 ? ? const hasKey = hasOwn(target, key) ? ? const result = Reflect.deleteProperty(target, key) ? ? if (hasKey && result) { ? ? ? ? // 觸發(fā)更新... ? ? ? ? trigger(target, key) ? ? } ? ? return result; }
2.ref
ref接收一個參數(shù)可以是原始值也可以是一個對象,如果傳入的是對象并且是ref創(chuàng)建的對象則直接返回,如果是普通對象則調(diào)用reactive來創(chuàng)建響應(yīng)式對象,否則創(chuàng)建一個只有value屬性的響應(yīng)式對象。
export function ref (raw) { ? ? // 判斷raw是否是ref創(chuàng)建的對象,如果是直接返回 ? ? if (isObject(raw) && raw.__v__isRef) { ? ? ? ? return raw ? ? } ? ? // 之前已經(jīng)定義過convert函數(shù),如果參數(shù)是對象就會調(diào)用reactive函數(shù)創(chuàng)建響應(yīng)式 ? ? let value = convert(raw); ? ? const r = { ? ? ? ? __v__isRef: true, ? ? ? ? get value () { ? ? ? ? ? ? track(r, 'value') ? ? ? ? ? ? return value ? ? ? ? }, ? ? ? ? set value (newValue) { ? ? ? ? ? ? // 判斷新值和舊值是否相等 ? ? ? ? ? ? if (newValue !== value) { ? ? ? ? ? ? ? ? raw = newValue ? ? ? ? ? ? ? ? value = convert(raw) ? ? ? ? ? ? ? ? // 觸發(fā)更新 ? ? ? ? ? ? ? ? trigger(r, 'value') ? ? ? ? ? ? } ? ? ? ? } ? ? } ? ? return r }
3.toRefs
toRefs
接收reactive
函數(shù)返回的響應(yīng)式對象,如果不是響應(yīng)式對象則直接返回。將傳入對象的所有屬性轉(zhuǎn)換成一個類似ref返回的對象將準換后的屬性掛載到一個新的對象上返回。
export function toRefs (proxy) { ? ? // 如果是數(shù)組創(chuàng)建一個相同長度的數(shù)組,否則返回一個空對象 ? ? const ret = proxy instanceof Array ? new Array(proxy.length) : {} ? ? for (const key in proxy) { ? ? ? ? ret[key] = toProxyRef(proxy, key) ? ? } ? ? return ret; } function toProxyRef (proxy, key) { ? ? const r = { ? ? ? ? __v__isRef: true, ? ? ? ? get value () { // 這里已經(jīng)是響應(yīng)式對象了,所以不需要再收集依賴了 ? ? ? ? ? ? return proxy[key] ? ? ? ? }, ? ? ? ? set value (newValue) { ? ? ? ? ? ? proxy[key] = newValue ? ? ? ? } ? ? } ? ? return r }
toRefs
的作用其實是將reactive
中的每個屬性都變成響應(yīng)式的。reactive方法會創(chuàng)建一個響應(yīng)式的對象,但是如果將reactive返回的對象進行解構(gòu)使用就不再
是響應(yīng)式了,toRefs
的作用就是支持解構(gòu)之后仍舊為響應(yīng)式。
4.computed
接著再來模擬一下computed
函數(shù)的內(nèi)部實現(xiàn)
computed
需要接收一個有返回值的函數(shù)作為參數(shù),這個函數(shù)的返回值就是計算屬性的值,需要監(jiān)聽函數(shù)內(nèi)部響應(yīng)式數(shù)據(jù)的變化,最后將函數(shù)執(zhí)行的結(jié)果返回。
export function computed (getter) { ? ? const result = ref() ? ? effect(() => (result.value = getter())) ? ? return result }
computed
函數(shù)會通過effect
監(jiān)聽getter
內(nèi)部響應(yīng)式數(shù)據(jù)的變化,因為在effect中執(zhí)行g(shù)etter的時候訪問響應(yīng)式數(shù)據(jù)的屬性會去收集依賴,當數(shù)據(jù)變化會重新執(zhí)行effect函數(shù),將getter的結(jié)果再存儲到result中。
到此這篇關(guān)于vue3.0響應(yīng)式函數(shù)原理詳細的文章就介紹到這了,更多相關(guān)vue3.0響應(yīng)式函數(shù)原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue?button的@click方法無效鉤子函數(shù)沒有執(zhí)行問題
這篇文章主要介紹了vue?button的@click方法無效鉤子函數(shù)沒有執(zhí)行問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03