您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關vue3.0響應式函數原理是什么,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
前言:
Vue3重寫了響應式系統,和Vue2相比底層采用Proxy
對象實現,在初始化的時候不需要遍歷所有的屬性再把屬性通過defineProperty轉換成get和set。另外如果有多層屬性嵌套的話只有訪問某個屬性的時候才會遞歸處理下一級的屬性所以Vue3中響應式系統的性能要比Vue2好。
接下來我們自己實現Vue3響應式系統的核心函數(reactive/ref/toRefs/computed/effect/track/trigger)
來學習一下響應式原理。
首先我們使用Proxy來實現響應式中的第一個函數reactive
。
reactive
接收一個參數,首先要判斷這個參數是否是一個對象,如果不是直接返回,reactive
只能將對象轉換成響應式對象,這是和ref不同的地方。
接著會創建攔截器對象handler, 其中抱哈get
,set
,deleteProperty
等攔截方法,最后創建并返回Proxy對象。
// 判斷是否是一個對象 const isObject = val => val !== null && typeof val === 'object' // 如果是對象則調用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是否相等,如果不相等再去覆蓋舊值,并且觸發更新 if (oldValue !== value) { result = Reflect.set(target, key, value, receiver) // 觸發更新... } // set方法需要返回布爾值 return result; }, deleteProperty (target, key) { // 首先要判斷當前target中是否有自己的key屬性 // 如果存在key屬性,并且刪除要觸發更新 const hasKey = hasOwn(target, key) const result = Reflect.deleteProperty(target, key) if (hasKey && result) { // 觸發更新... } return result; } } return new Proxy(target, handler) }
至此reactive
函數就寫完了,接著我們來編寫一下收集依賴的過程。
在依賴收集的過程會創建三個集合,分別是targetMap
,depsMap
以及dep
。
其中targetMap
是用來記錄目標對象和字典他使用的是weakMap
,key是目標對象,targetMap的值是depsMap, 類型是Map,這里面的key是目標對象的屬性名稱,值是一個Set集合,集合中存儲的元素是Effect函數。因為可以多次調用同一個Effect在Effect訪問同一個屬性,這個時候這個屬性會收集多次依賴對應多個Effect函數。
一個屬性可以對應多個Effect函數,觸發更新的時候可以通過屬性找到對應的Effect函數,進行執行。
我們這里分別來實現一下effect
和track
兩個函數。
effect函數接收一個函數作為參數,我們首先在外面定一個變量存儲callback, 這樣track函數就可以訪問到callback了。
let activeEffect = null; export function effect (callback) { activeEffect = callback; // 訪問響應式對象屬性,收集依賴 callback(); // 依賴收集結束要置null activeEffect = null; }
track
函數接收兩個參數目標對象和屬性, 他的內部要將target
存儲到targetMap
中。需要先定義一個這樣的Map。
let targetMap = new WeakMap() export function track (target, key) { // 判斷activeEffect是否存在 if (!activeEffect) { return; } // depsMap存儲對象和effect的對應關系 let depsMap = targetMap.get(target) // 如果不存在則創建一個map存儲到targetMap中 if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } // 根據屬性查找對應的dep對象 let dep = depsMap.get(key) // dep是一個集合,用于存儲屬性所對應的effect函數 if (!dep) { // 如果不存在,則創建一個新的集合添加到depsMap中 depsMap.set(key, (dep = new Set())) } dep.add(activeEffect) }
track
是依賴收集的函數。需要在reactive
函數的get方法中調用。
get (target, key, receiver) { // 收集依賴 track(target, key) const result = Reflect.get(target, key, receiver) // 如果屬性是對象則需要遞歸處理 return convert(result) },
這樣整個依賴收集就完成了。接著就要實現觸發更新,對應的函數是trigger,這個過程和track的過程正好相反。
trigger
函數接收兩個參數,分別是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函數要在reactive函數中的set
和deleteProperty
中觸發。
set (target, key, value, receiver) { const oldValue = Reflect.get(target, key, receiver) let result = true; // 需要判斷當前傳入的新值和oldValue是否相等,如果不相等再去覆蓋舊值,并且觸發更新 if (oldValue !== value) { result = Reflect.set(target, key, value, receiver) // 觸發更新... trigger(target, key) } // set方法需要返回布爾值 return result; }, deleteProperty (target, key) { // 首先要判斷當前target中是否有自己的key屬性 // 如果存在key屬性,并且刪除要觸發更新 const hasKey = hasOwn(target, key) const result = Reflect.deleteProperty(target, key) if (hasKey && result) { // 觸發更新... trigger(target, key) } return result; }
ref接收一個參數可以是原始值也可以是一個對象,如果傳入的是對象并且是ref創建的對象則直接返回,如果是普通對象則調用reactive來創建響應式對象,否則創建一個只有value屬性的響應式對象。
export function ref (raw) { // 判斷raw是否是ref創建的對象,如果是直接返回 if (isObject(raw) && raw.__v__isRef) { return raw } // 之前已經定義過convert函數,如果參數是對象就會調用reactive函數創建響應式 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) // 觸發更新 trigger(r, 'value') } } } return r }
toRefs
接收reactive
函數返回的響應式對象,如果不是響應式對象則直接返回。將傳入對象的所有屬性轉換成一個類似ref返回的對象將準換后的屬性掛載到一個新的對象上返回。
export function toRefs (proxy) { // 如果是數組創建一個相同長度的數組,否則返回一個空對象 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 () { // 這里已經是響應式對象了,所以不需要再收集依賴了 return proxy[key] }, set value (newValue) { proxy[key] = newValue } } return r }
toRefs
的作用其實是將reactive
中的每個屬性都變成響應式的。reactive方法會創建一個響應式的對象,但是如果將reactive返回的對象進行解構使用就不再
是響應式了,toRefs
的作用就是支持解構之后仍舊為響應式。
接著再來模擬一下computed
函數的內部實現
computed
需要接收一個有返回值的函數作為參數,這個函數的返回值就是計算屬性的值,需要監聽函數內部響應式數據的變化,最后將函數執行的結果返回。
export function computed (getter) { const result = ref() effect(() => (result.value = getter())) return result }
computed
函數會通過effect
監聽getter
內部響應式數據的變化,因為在effect中執行getter的時候訪問響應式數據的屬性會去收集依賴,當數據變化會重新執行effect函數,將getter的結果再存儲到result中。
關于“vue3.0響應式函數原理是什么”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。