vue3響應(yīng)式原理之Ref用法及說明
theme: fancy
一. Ref 用法
這是 ref 最基本的用法,返回來的count是一個響應(yīng)式的代理值
const count = ref(0)
二. 實現(xiàn)
1. ref 函數(shù)
我們調(diào)用的ref函數(shù),傳進來一個 val 值,調(diào)用 createRef 函數(shù),我們來看下該函數(shù)的實現(xiàn)
源碼路徑:packages/reactivity/src/ref.ts
function ref(value?: unknown) { ? return createRef(value, false) }
2. createRef 函數(shù)
該函數(shù)里邊做了一個判斷,該值如果已經(jīng)是一個Ref,則直接返回,否則創(chuàng)建一個 Ref 實例,讓我們看下 RefImpl 類的實現(xiàn)
源碼路徑:packages/reactivity/src/ref.ts
function createRef(rawValue: unknown, shallow: boolean) { ? if (isRef(rawValue)) { ? ? return rawValue ? } ? return new RefImpl(rawValue, shallow) }
3. RefImpl 類的實現(xiàn)
定義了四個變量
_value
: 用來保存加工后實現(xiàn)響應(yīng)化的值_rawValue
: 用來保存當前未經(jīng)加工過的值dep
: 用來收集依賴,是一個 Set類型_v_isRef
: 用來標識該值是否經(jīng)過 ref 加工
在constructor中,我們?yōu)開rawValue 和_value實現(xiàn)了初始化,分別調(diào)用了toRaw和toReactive函數(shù),toReactive將 object類型轉(zhuǎn)換為響應(yīng)式,所以ref中也可以實現(xiàn)對象的響應(yīng)式管理,我們將他放在下篇文章中來講,它是實現(xiàn)對象響應(yīng)式的主要方法,我們今天只講基本類型。
源碼路徑:packages/reactivity/src/ref.ts
class RefImpl<T> { ? private _value: T ? private _rawValue: T ? public dep?: Dep = undefined ? public readonly __v_isRef = true ? constructor(value: T, public readonly __v_isShallow: boolean) { ? ? this._rawValue = __v_isShallow ? value : toRaw(value) ? ? this._value = __v_isShallow ? value : toReactive(value) ? } ? get value() { ? ? trackRefValue(this) ? ? return this._value ? } ? set value(newVal) { ? ? newVal = this.__v_isShallow ? newVal : toRaw(newVal) ? ? if (hasChanged(newVal, this._rawValue)) { ? ? ? this._rawValue = newVal ? ? ? this._value = this.__v_isShallow ? newVal : toReactive(newVal) ? ? ? triggerRefValue(this, newVal) ? ? } ? } }
4. trackRefValue 依賴收集
從上邊代碼中我們可以看到,我們使用 get 和 set對 value 屬性實現(xiàn)了攔截,其實如同Object.defineProperty,在 get中實現(xiàn)依賴收集,set中通知依賴更新,而 vue3中是通過調(diào)用trackRefValue來實現(xiàn)跟蹤依賴。
在該方法中,調(diào)用了trackEffect方法,在收集第一個依賴時執(zhí)行createDep方法來作為參數(shù)傳入。
我們接著往下看。
源碼路徑:packages/reactivity/src/ref.ts
function trackRefValue(ref: RefBase<any>) { ? if (shouldTrack && activeEffect) { ? ? ref = toRaw(ref) ? ? if (__DEV__) { ? ? ? trackEffects(ref.dep || (ref.dep = createDep()), { ? ? ? ? target: ref, ? ? ? ? type: TrackOpTypes.GET, ? ? ? ? key: 'value' ? ? ? }) ? ? } else { ? ? ? trackEffects(ref.dep || (ref.dep = createDep())) ? ? } ? } }
5. createDep 創(chuàng)建依賴容器
其實就是創(chuàng)建了一個 Set 類型,之后我們進入到下一步 trackEffects。
源碼路徑:packages/reactivity/src/dep.ts
export const createDep = (effects?: ReactiveEffect[]): Dep => { ? const dep = new Set<ReactiveEffect>(effects) as Dep ? dep.w = 0 ? dep.n = 0 ? return dep }
6. trackEffects
可以看到這里我們將activeEffect 放入的我們的Ref的dep中,也就是Set類型,那這個activeEffect是什么呢?
它其實就是我們的componentUpdateFn,每次更改value值時,通知對應(yīng)的組件更新。我們來看下activeEffect是如何賦值的。
源碼路徑:packages/reactivity/src/effect.ts
export function trackEffects( ? dep: Dep, ? debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { ? if (shouldTrack) { ? ? // 放入我們的依賴集合? ? ? dep.add(activeEffect!) ? ? activeEffect!.deps.push(dep) }
三. activeEffect 的賦值
我們在上邊說過,activeEffect其實就是componentUpdateFn函數(shù),所以該值應(yīng)該是一個變化的值,它是如何準確無誤的將每個組件更新函數(shù)來放入到對應(yīng)的dep中的呢,我們回到setupRenderEffect函數(shù)里邊來看一下。
//源碼路徑 core/packages/runtime-core/src/renderer.ts ? const setupRenderEffect: SetupRenderEffectFn = ( ? ? instance, ? ? initialVNode, ? ? container, ? ? anchor, ? ? parentSuspense, ? ? isSVG, ? ? optimized ? ) => { ? ? const componentUpdateFn = () => { } ? ? // create reactive effect for rendering ? ? const effect = (instance.effect = new ReactiveEffect( ? ? ? componentUpdateFn, ? ? ? () => queueJob(instance.update), ? ? ? instance.scope // track it in component's effect scope ? ? )) ? ? const update = (instance.update = effect.run.bind(effect) as SchedulerJob) ? ? update.id = instance.uid ? ? // allowRecurse ? ? // #1801, #2043 component render effects should allow recursive updates ? ? toggleRecurse(instance, true) ? ? update() ? }
從上邊源碼中我們可以看到,在setRenderEffectFn方法中實現(xiàn)了componentUpdateFn的定義,同時在此時創(chuàng)建了一個ReactiveEfect的對象,同時調(diào)用了ReactiveEffect中的run方法,并且使用bind來改變其中的this指向該effect。我們來進一步看下run中發(fā)生了什么。
源碼路徑:packages/reactivity/src/effect.ts
? run() { ? ? if (!this.active) { ? ? ? return this.fn() ? ? } ? ? let parent: ReactiveEffect | undefined = activeEffect ?? ? ? try { ? ? ? this.parent = activeEffect ? ? ? activeEffect = this ? ? ? shouldTrack = true ? ? ? trackOpBit = 1 << ++effectTrackDepth ? ? ? if (effectTrackDepth <= maxMarkerBits) { ? ? ? ? initDepMarkers(this) ? ? ? } else { ? ? ? ? cleanupEffect(this) ? ? ? } ? ? ? return this.fn() ? ? } finally { ? ? ? if (effectTrackDepth <= maxMarkerBits) { ? ? ? ? finalizeDepMarkers(this) ? ? ? } ? ? ? trackOpBit = 1 << --effectTrackDepth ? ? ? activeEffect = this.parent ? ? ? shouldTrack = lastShouldTrack ? ? ? this.parent = undefined ? ? } ? }
從上邊代碼中我們看到,在try中,我們先將this賦值給activeEffect,然后調(diào)用傳入的componentUpdateFn函數(shù),在該函數(shù)中會獲取我們定義的ref變量的值,觸發(fā)get,然后將該this也就是effect保存到該Ref的dep中,這就完成了我們的整個依賴收集的過程。
四. 依賴收集
收集依賴后,下一步就是在數(shù)據(jù)改變之后通知依賴更新,我們來看下set中是如何做的。
在set中我們調(diào)用了triggerRefValue方法,傳入了this,也就是當前Ref實例,還有新的值。
源碼路徑:packages/reactivity/src/ref.ts
? set value(newVal) { ? ? newVal = this.__v_isShallow ? newVal : toRaw(newVal) ? ? if (hasChanged(newVal, this._rawValue)) { ? ? ? this._rawValue = newVal ? ? ? this._value = this.__v_isShallow ? newVal : toReactive(newVal) ? ? ? triggerRefValue(this, newVal) ? ? } ? }
1. triggerRefValue
該方法中調(diào)用了triggerEffects方法,將ref.dep也就是之前收集依賴的Set,傳入。讓我們接著往下看。
// 源碼路徑:packages/reactivity/src/ref.ts export function triggerRefValue(ref: RefBase<any>, newVal?: any) { ? ref = toRaw(ref) ? if (ref.dep) { ? ? if (__DEV__) { ? ? ? triggerEffects(ref.dep, { ? ? ? ? target: ref, ? ? ? ? type: TriggerOpTypes.SET, ? ? ? ? key: 'value', ? ? ? ? newValue: newVal ? ? ? }) ? ? } else { ? ? ? triggerEffects(ref.dep) ? ? } ? } }
2. triggerEffects
該方法中,遍歷Set,然后調(diào)用每個依賴上的run方法,也就是執(zhí)行更新函數(shù),進而使頁面刷新。實現(xiàn)數(shù)據(jù)到頁面的響應(yīng)式渲染。
// 源碼路徑:packages/reactivity/src/effect.ts export function triggerEffects( ? dep: Dep | ReactiveEffect[], ? debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { ? // spread into array for stabilization ? for (const effect of isArray(dep) ? dep : [...dep]) { ? ? if (effect !== activeEffect || effect.allowRecurse) { ? ? ? if (__DEV__ && effect.onTrigger) { ? ? ? ? effect.onTrigger(extend({ effect }, debuggerEventExtraInfo)) ? ? ? } ? ? ? if (effect.scheduler) { ? ? ? ? effect.scheduler() ? ? ? } else { ? ? ? ? effect.run() ? ? ? } ? ? } ? } }
五. 總結(jié)
vue3 中對Ref的實現(xiàn)基本沒有太大改變,也是利用setter和getter對數(shù)據(jù)實現(xiàn)數(shù)據(jù)劫持,而Reactive的響應(yīng)式原理就與vue2的方案截然不同了。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
vue computed計算屬性顯示undefined的解決
這篇文章主要介紹了vue computed計算屬性顯示undefined的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11vue實現(xiàn)彈框遮罩點擊其他區(qū)域彈框關(guān)閉及v-if與v-show的區(qū)別介紹
vue如何簡單的實現(xiàn)彈框,遮罩,點擊其他區(qū)域關(guān)閉彈框, 簡單的思路是以一個div作為遮罩,這篇文章給大家詳細介紹了vue實現(xiàn)彈框遮罩點擊其他區(qū)域彈框關(guān)閉及v-if與v-show的區(qū)別介紹,感興趣的朋友一起看看吧2018-09-09vue項目使用高德地圖的定位及關(guān)鍵字搜索功能的實例代碼(踩坑經(jīng)驗)
這篇文章主要介紹了vue項目使用高德地圖的定位及關(guān)鍵字搜索功能的實例代碼,也是小編踩了無數(shù)坑總結(jié)出來的經(jīng)驗,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03vue.js數(shù)據(jù)響應(yīng)式原理解析
這篇文章主要介紹了vue.js數(shù)據(jù)響應(yīng)式原理解析,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的朋友可以參考一下2022-08-08vue2利用Bus.js如何實現(xiàn)非父子組件通信詳解
這篇文章主要給大家介紹了關(guān)于vue2利用Bus.js如何實現(xiàn)非父子組件通信的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧。2017-08-08Vue實現(xiàn)動態(tài)圓環(huán)百分比進度條
這篇文章主要為大家詳細介紹了Vue實現(xiàn)動態(tài)圓環(huán)百分比進度條,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-09-09