vue3中effect函數(shù)到底是什么詳解
前言
vue3中的effect
函數(shù)是響應(yīng)式的核心,它被稱作副作用函數(shù),那么什么是副作用函數(shù)呢?我們來看一下。
vue中,數(shù)據(jù)target改變,可能會(huì)引起其他數(shù)據(jù)或者視圖發(fā)生變化,那么,其他數(shù)據(jù)改變以及視圖改變這些效果就是target改變的副作用。像watch、computed、這些都是會(huì)產(chǎn)生副作用的函數(shù),它們的底層都是使用了effect。
我們先來看看effect
的原理。首先先來認(rèn)識(shí)幾個(gè)重要的全局變量。
targetMap
targetMap是一個(gè)WeakMap,存儲(chǔ)了{(lán)target -> key -> dep}的關(guān)系。targetMap的key是需要做響應(yīng)式處理的原始對(duì)象,targetMap的value是一個(gè)Map,Map的key是原始對(duì)象的屬性,Map的value是每個(gè)屬性關(guān)聯(lián)的副作用函數(shù)Set。副作用函數(shù)就是我們需要追蹤的依賴,也就是訂閱者。
activeEffect
activeEffect保存當(dāng)前正在執(zhí)行的副作用函數(shù),它是一個(gè)對(duì)象,effect的類型如下:
let activeEffect: ReactiveEffect | undefined // effect類型 class ReactiveEffect<T = any> { active = true deps: Dep[] = [] parent: ReactiveEffect | undefined = undefined constructor( public fn: () => T, public scheduler: EffectScheduler | null = null, scope?: EffectScope ) { recordEffectScope(this, scope) } run(){} stop() {} }
shouldTrack
shouldTrack 變量用來標(biāo)識(shí)是否開啟依賴搜集,只有 shouldTrack 的值為 true 時(shí),才進(jìn)行依賴收集,即將副作用函數(shù)添加到依賴集合中。初始化值是true。當(dāng)執(zhí)行run()時(shí),會(huì)將shouldTrack設(shè)置為true,開啟依賴收集。
effect
接下來看看副作用函數(shù)effect的實(shí)現(xiàn)原理。當(dāng)依賴的數(shù)據(jù)變化的時(shí)候副作用函數(shù)便會(huì)觸發(fā),想當(dāng)然的肯定會(huì)涉及到依賴收集收集與追蹤,因此之后我們還會(huì)講解一下track、trigger與effect的關(guān)系。
interface ReactiveEffectRunner<T = any> { (): T // ReactiveEffect就是上面提到的activeEffect的類型 effect: ReactiveEffect } function effect<T = any>( fn: () => T, options?: ReactiveEffectOptions ): ReactiveEffectRunner { // 如果傳入的fn本身就是effect,那么就直接執(zhí)行 effect的副作用 if ((fn as ReactiveEffectRunner).effect) { fn = (fn as ReactiveEffectRunner).effect.fn } // 將fn包裝成effect const _effect = new ReactiveEffect(fn) if (options) { extend(_effect, options) if (options.scope) recordEffectScope(_effect, options.scope) } // 不是延遲執(zhí)行,直接執(zhí)行副作用函數(shù) if (!options || !options.lazy) { _effect.run() } const runner = _effect.run.bind(_effect) as ReactiveEffectRunner runner.effect = _effect return runner }
fn
就是傳給effect
的副作用,將fn
傳給ReactiveEffect
包裝一下,將其包裝成標(biāo)準(zhǔn)的effect
類型。標(biāo)準(zhǔn)的effect
類型是ReactiveEffect
類,具有active、deps、deps、run()、stop()
這些屬性方法。其中run
方法中就是執(zhí)行傳入的副作用函數(shù)fn
。 但是effect
函數(shù)返回的不是ReactiveEffect
類型,而是ReactiveEffectRunner
類型,接口ReactiveEffectRunner
中具有effect
屬性,它是ReactiveEffect
類型,因此effect最終需要runner.effect = _effect,然后返回runner。
我們傳入的副作用在effect的run()中執(zhí)行。需要重點(diǎn)看一下run。
run() { if (!this.active) { return this.fn() } let parent: ReactiveEffect | undefined = activeEffect let lastShouldTrack = shouldTrack while (parent) { if (parent === this) { return } parent = parent.parent } try { this.parent = activeEffect // 全局activeEffect指向當(dāng)前執(zhí)行的自身 activeEffect = this // 開啟依賴收集 shouldTrack = true trackOpBit = 1 << ++effectTrackDepth if (effectTrackDepth <= maxMarkerBits) { initDepMarkers(this) } else { cleanupEffect(this) } // 執(zhí)行副作用函數(shù) return this.fn() } finally { if (effectTrackDepth <= maxMarkerBits) { finalizeDepMarkers(this) } trackOpBit = 1 << --effectTrackDepth activeEffect = this.parent shouldTrack = lastShouldTrack this.parent = undefined if (this.deferStop) { this.stop() } } }
執(zhí)行run
的時(shí)候,將全局activeEffect
指向自身,也就是當(dāng)前執(zhí)行的effect
,然后開啟依賴收集標(biāo)識(shí)位,執(zhí)行副作用函數(shù)。
說到這,我們好像還沒發(fā)現(xiàn)effect與track
和trigger
有什么關(guān)系。track、trigger
函數(shù)與effect
函數(shù)都是位于源碼同一個(gè)文件下的,那么它們肯定是有關(guān)聯(lián)的。接下來,我們看看track
的邏輯。
track(target: object, type: TrackOpTypes, key: unknown) { if (shouldTrack && activeEffect) { let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = createDep())) } const eventInfo = __DEV__ ? { effect: activeEffect, target, type, key } : undefined trackEffects(dep, eventInfo) } }
我們知道,在研究vue
的雙向數(shù)據(jù)綁定時(shí),需要進(jìn)行依賴收集與依賴追蹤。track
就是依賴收集的過程,當(dāng)我們通過響應(yīng)式API初始化數(shù)據(jù)或者模板中渲染一些變量的時(shí)候,就需要進(jìn)行依賴收集,也就是track
。依賴,也就是我們的副作用函數(shù),依賴收集的過程其實(shí)就是收集副作用函數(shù)的過程。而副作用,就是我們剛剛上文講的effect
。這就關(guān)聯(lián)上了。 剛剛研究effect
的時(shí)候,提到了一些全局變量,其中targetMap
存儲(chǔ)了{target -> key -> dep}
的關(guān)系,我們依賴收集的過程,就是將副作用存儲(chǔ)起來的過程。因此在track中,傳入的參數(shù)target對(duì)應(yīng)的就是targetMap
的key。先查看全局變量targetMap
中是否存在該target
,沒有的話就初始化map賦值。同理,通過key來尋找最終的副作用Set,沒有的話就初始化設(shè)置。至此,我們完善了{target -> key -> dep}
的關(guān)系,接下來看看 trackEffects
。
export function trackEffects( dep: Dep, debuggerEventExtraInfo?: DebuggerEventExtraInfo ) { let shouldTrack = false if (effectTrackDepth <= maxMarkerBits) { if (!newTracked(dep)) { dep.n |= trackOpBit // set newly tracked shouldTrack = !wasTracked(dep) } } else { // Full cleanup mode. shouldTrack = !dep.has(activeEffect!) } if (shouldTrack) { dep.add(activeEffect!) activeEffect!.deps.push(dep) if (__DEV__ && activeEffect!.onTrack) { activeEffect!.onTrack({ effect: activeEffect!, ...debuggerEventExtraInfo! }) } } }
trackEffects
的邏輯其實(shí)就是將當(dāng)前激活的activeEffect
放到dep
中,這樣就完成了依賴收集過程。 trigger
過程就不多贅述了,就是根據(jù){target -> key -> dep}
的關(guān)系一層一層的找到最后的dep
,然后執(zhí)行其中的副作用函數(shù)即可。
說到這,可能還是有人不明白,如果不使用watch、computed
這些帶有副作用的 api, track 、trigger
與effect
又有什么關(guān)系呢?那track
收集的副作用又是什么呢?
說到這里我想說,關(guān)于副作用,除了watch、computed
這些api中用戶手動(dòng)傳入的fn算作副作用,頁(yè)面渲染也是副作用。我們使用vue中雙向數(shù)據(jù)綁定的特性,意味著當(dāng)我們定義一個(gè)響應(yīng)式變量,如果模板中使用了這個(gè)響應(yīng)式變量,那么這個(gè)變量在模板中的渲染是響應(yīng)式的,這個(gè)渲染是副作用,那么這個(gè)頁(yè)面渲染就需要被收集到dep中。如果這個(gè)變量還是作為 watch中fn的內(nèi)部出現(xiàn)的,那么這個(gè)變量的副作用又多了一個(gè),還需要被額外收集。所以dep是一個(gè)Set的數(shù)據(jù)結(jié)構(gòu)。當(dāng)這個(gè)變量變化的時(shí)候,頁(yè)面要重新渲染,watch也要重新計(jì)算,這就是trigger的過程。
當(dāng)然這些針對(duì)的是響應(yīng)式數(shù)據(jù),如果我們僅僅定義一個(gè)靜態(tài)數(shù)據(jù),那么是不需要進(jìn)行依賴收集的,它也不是響應(yīng)式的。 說到這里應(yīng)該可以明白,effect
與響應(yīng)式密切相關(guān)了。
computed
講完effect,我們不難知道computed、watch也是一種effect,只是參數(shù)不同而已。我們簡(jiǎn)單看一下computed。
constructor( getter: ComputedGetter<T>, private readonly _setter: ComputedSetter<T>, isReadonly: boolean, isSSR: boolean ) { // computed也是實(shí)例化effect this.effect = new ReactiveEffect(getter, () => { if (!this._dirty) { this._dirty = true triggerRefValue(this) } }) this.effect.computed = this this.effect.active = this._cacheable = !isSSR this[ReactiveFlags.IS_READONLY] = isReadonly }
從computed的構(gòu)造器中,我們可以看到computed也是實(shí)例化effect實(shí)現(xiàn)的,只是傳的參數(shù)不同而已,我們暫且稱作computedEffect??梢钥吹絚omputedEffect的computed屬性是true。
watch
注意:watch雖然依賴于effect函數(shù),但是在源碼目錄中卻不屬于reactivity目錄,而是runtime-core目錄下的。
const effect = new ReactiveEffect(getter, scheduler)
可以看到watch中同樣使用ReactiveEffect創(chuàng)建副作用,只不過多傳了scheduler參數(shù)。
總結(jié)
到此這篇關(guān)于vue3中effect函數(shù)到底是什么的文章就介紹到這了,更多相關(guān)vue3中effect函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue+elementui(對(duì)話框中form表單的reset問題)
這篇文章主要介紹了vue+elementui(對(duì)話框中form表單的reset問題),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-05-05Vue集成three.js并加載glb、gltf、FBX、json模型的場(chǎng)景分析
這篇文章主要介紹了Vue集成three.js,并加載glb、gltf、FBX、json模型,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09基于Vue3實(shí)現(xiàn)一個(gè)小相冊(cè)詳解
這篇文章主要為大家詳細(xì)介紹了如何基于Vue3實(shí)現(xiàn)一個(gè)小相冊(cè)效果,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-12-12vue 使用 canvas 實(shí)現(xiàn)手寫電子簽名
這篇文章主要介紹了vue 使用 canvas 實(shí)現(xiàn)手寫電子簽名功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03Vue.extend和VueComponent的關(guān)系源碼解析
這篇文章主要為大家詳解了Vue.extend和VueComponent的關(guān)系源碼解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02django+vue實(shí)現(xiàn)注冊(cè)登錄的示例代碼
這篇文章主要介紹了django+vue實(shí)現(xiàn)注冊(cè)登錄的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05關(guān)于axios配置多個(gè)請(qǐng)求地址(打包后可通過配置文件修改)
這篇文章主要介紹了關(guān)于axios配置多個(gè)請(qǐng)求地址(打包后可通過配置文件修改),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09使用kbone解決Vue項(xiàng)目同時(shí)支持小程序問題
這篇文章主要介紹了使用kbone解決Vue項(xiàng)目同時(shí)支持小程序問題,本文通過一個(gè)todo的例子跟大家詳細(xì)介紹,需要的朋友可以參考下2019-11-11詳解Electron中如何使用SQLite存儲(chǔ)筆記
這篇文章主要為大家介紹了Electron中如何使用SQLite存儲(chǔ)筆記示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11