您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“Vue3 effectScope API實現(xiàn)原理是什么”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
其官方的描述是創(chuàng)建一個 effect 作用域,可以捕獲其中所創(chuàng)建的響應(yīng)式副作用 (即計算屬性和偵聽器),這樣捕獲到的副作用可以一起處理。并給出了示例:
const scope = effectScope() scope.run(() => { const doubled = computed(() => counter.value * 2) watch(doubled, () => console.log(doubled.value)) watchEffect(() => console.log('Count: ', doubled.value)) }) // 處理掉當(dāng)前作用域內(nèi)的所有 effect scope.stop()
我們就從這個示例入手看看具體的源碼實現(xiàn):
// packages/reactivity/src/effectScope.ts export function effectScope(detached?: boolean) { // 返回EffectScope實例 return new EffectScope(detached) }
EffectScope
export class EffectScope { /** * @internal */ private _active = true /** * @internal */ effects: ReactiveEffect[] = [] /** * @internal */ cleanups: (() => void)[] = [] /** * only assigned by undetached scope * @internal */ parent: EffectScope | undefined /** * record undetached scopes * @internal */ scopes: EffectScope[] | undefined /** * track a child scope's index in its parent's scopes array for optimized * // index作用:在父作用域數(shù)組中跟蹤子作用域范圍索引以進行優(yōu)化。 * removal * @internal */ private index: number | undefined constructor(public detached = false) { // 記錄當(dāng)前scope為parent scope this.parent = activeEffectScope if (!detached && activeEffectScope) { this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push( this ) - 1 } } get active() { return this._active } run<T>(fn: () => T): T | undefined { if (this._active) { const currentEffectScope = activeEffectScope try { activeEffectScope = this return fn() } finally { activeEffectScope = currentEffectScope } } else if (__DEV__) { warn(`cannot run an inactive effect scope.`) } } /** * This should only be called on non-detached scopes * 必須在非分離的作用域上調(diào)用 * @internal */ on() { activeEffectScope = this } /** * This should only be called on non-detached scopes * @internal */ off() { activeEffectScope = this.parent } // stop方法 stop(fromParent?: boolean) { if (this._active) { let i, l // stop effects for (i = 0, l = this.effects.length; i < l; i++) { this.effects[i].stop() } // 執(zhí)行所有的cleanups for (i = 0, l = this.cleanups.length; i < l; i++) { this.cleanups[i]() } // 遞歸停止所有的子作用域 if (this.scopes) { for (i = 0, l = this.scopes.length; i < l; i++) { this.scopes[i].stop(true) } } // nested scope, dereference from parent to avoid memory leaks if (!this.detached && this.parent && !fromParent) { // optimized O(1) removal const last = this.parent.scopes!.pop() if (last && last !== this) { this.parent.scopes![this.index!] = last last.index = this.index! } } this.parent = undefined this._active = false } } }
在執(zhí)行scope.run的時候會將this賦值到全局的activeEffectScope變量,然后執(zhí)行傳入函數(shù)。對于computed、watch、watchEffect(watchEffect是調(diào)用doWatch實現(xiàn)的,與watch實現(xiàn)響應(yīng)式綁定的方式相同)這些API都會創(chuàng)建ReactiveEffect實例來建立響應(yīng)式關(guān)系,而收集對應(yīng)的響應(yīng)式副作用就發(fā)生在ReactiveEffect創(chuàng)建的時候,我們來看一下ReactiveEffect的構(gòu)造函數(shù):
// ReactiveEffect的構(gòu)造函數(shù) constructor( public fn: () => T, public scheduler: EffectScheduler | null = null, scope?: EffectScope ) { // effect實例默認(rèn)會被記錄到指定scope中 // 如果沒有指定scope則會記錄到全局activeEffectScope中 recordEffectScope(this, scope) } // recordEffectScope實現(xiàn) export function recordEffectScope( effect: ReactiveEffect, // scope默認(rèn)值為activeEffectScope scope: EffectScope | undefined = activeEffectScope ) { if (scope && scope.active) { scope.effects.push(effect) } }
可以看到如果我們沒有傳入scope參數(shù),那么在執(zhí)行recordEffectScope時就會有一個默認(rèn)的參數(shù)為activeEffectScope,這個值不正是我們scope.run的時候賦值的嗎!所以新創(chuàng)建的effect會被放到activeEffectScope.effects中,這就是響應(yīng)式副作用的收集過程。
那么對于一起處理就比較簡單了,只需要處理scope.effects即可
日常開發(fā)中其實并不需要我們關(guān)心組件副作用的收集和清除,因為這些操作是已經(jīng)內(nèi)置好的,我們來看一下源碼中是怎么做的
在組件實例創(chuàng)建的時候就已經(jīng)new了一個屬于自已的scope對象了:
const instance: ComponentInternalInstance = { ... // 初始化scope scope: new EffectScope(true /* detached */), ... }
在我們執(zhí)行setup之前,會調(diào)用setCurrentInstance,他會調(diào)用instance.scope.on,那么就會將activeEffectScope賦值為instance.scope,那么在setup中注冊的computed、watch等就都會被收集到instance.scope.effects
function setupStatefulComponent( instance: ComponentInternalInstance, isSSR: boolean ) { // 組件對象 const Component = instance.type as ComponentOptions ... // 2. call setup() const { setup } = Component if (setup) { // 創(chuàng)建setupContext const setupContext = (instance.setupContext = // setup參數(shù)個數(shù)判斷 大于一個參數(shù)創(chuàng)建setupContext setup.length > 1 ? createSetupContext(instance) : null) // instance賦值給currentInstance // 設(shè)置當(dāng)前實例為instance 為了在setup中可以通過getCurrentInstance獲取到當(dāng)前實例 // 同時開啟instance.scope.on() setCurrentInstance(instance) // 暫停tracking 暫停收集副作用函數(shù) pauseTracking() // 執(zhí)行setup const setupResult = callWithErrorHandling( setup, instance, ErrorCodes.SETUP_FUNCTION, // setup參數(shù) [__DEV__ ? shallowReadonly(instance.props) : instance.props, setupContext] ) // 重新開啟副作用收集 resetTracking() // currentInstance置為空 // activeEffectScope賦值為instance.scope.parent // 同時instance.scope.off() unsetCurrentInstance() ... } else { finishComponentSetup(instance, isSSR) } }
對于選項式API的收集是同樣的操作:
// support for 2.x options if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) { setCurrentInstance(instance) pauseTracking() // 處理options API applyOptions(instance) resetTracking() unsetCurrentInstance() }
完成了收集那么對于清理就只需要在組件卸載的時候執(zhí)行stop方法即可:
// packages/runtime-core/src/renderer.ts const unmountComponent = ( instance: ComponentInternalInstance, parentSuspense: SuspenseBoundary | null, doRemove?: boolean ) => { if (__DEV__ && instance.type.__hmrId) { unregisterHMR(instance) } const { bum, scope, update, subTree, um } = instance ... // stop effects in component scope // 副作用清除 scope.stop() ... }
“Vue3 effectScope API實現(xiàn)原理是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。