Vue3源碼解讀effectScope API及實(shí)現(xiàn)原理
vue3新增effectScope相關(guān)的API
其官方的描述是創(chuàng)建一個(gè) effect 作用域,可以捕獲其中所創(chuàng)建的響應(yīng)式副作用 (即計(jì)算屬性和偵聽(tī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()
我們就從這個(gè)示例入手看看具體的源碼實(shí)現(xiàn):
effectScope
// packages/reactivity/src/effectScope.ts
export function effectScope(detached?: boolean) {
// 返回EffectScope實(shí)例
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ù)組中跟蹤子作用域范圍索引以進(jìn)行優(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的時(shí)候會(huì)將this賦值到全局的activeEffectScope變量,然后執(zhí)行傳入函數(shù)。對(duì)于computed、watch、watchEffect(watchEffect是調(diào)用doWatch實(shí)現(xiàn)的,與watch實(shí)現(xiàn)響應(yīng)式綁定的方式相同)這些API都會(huì)創(chuàng)建ReactiveEffect實(shí)例來(lái)建立響應(yīng)式關(guān)系,而收集對(duì)應(yīng)的響應(yīng)式副作用就發(fā)生在ReactiveEffect創(chuàng)建的時(shí)候,我們來(lái)看一下ReactiveEffect的構(gòu)造函數(shù):
// ReactiveEffect的構(gòu)造函數(shù)
constructor(
public fn: () => T,
public scheduler: EffectScheduler | null = null,
scope?: EffectScope
) {
// effect實(shí)例默認(rèn)會(huì)被記錄到指定scope中
// 如果沒(méi)有指定scope則會(huì)記錄到全局activeEffectScope中
recordEffectScope(this, scope)
}
// recordEffectScope實(shí)現(xiàn)
export function recordEffectScope(
effect: ReactiveEffect,
// scope默認(rèn)值為activeEffectScope
scope: EffectScope | undefined = activeEffectScope
) {
if (scope && scope.active) {
scope.effects.push(effect)
}
}
可以看到如果我們沒(méi)有傳入scope參數(shù),那么在執(zhí)行recordEffectScope時(shí)就會(huì)有一個(gè)默認(rèn)的參數(shù)為activeEffectScope,這個(gè)值不正是我們scope.run的時(shí)候賦值的嗎!所以新創(chuàng)建的effect會(huì)被放到activeEffectScope.effects中,這就是響應(yīng)式副作用的收集過(guò)程。
那么對(duì)于一起處理就比較簡(jiǎn)單了,只需要處理scope.effects即可
組件的scope
日常開(kāi)發(fā)中其實(shí)并不需要我們關(guān)心組件副作用的收集和清除,因?yàn)檫@些操作是已經(jīng)內(nèi)置好的,我們來(lái)看一下源碼中是怎么做的
組件實(shí)例中的scope
在組件實(shí)例創(chuàng)建的時(shí)候就已經(jīng)new了一個(gè)屬于自已的scope對(duì)象了:
const instance: ComponentInternalInstance = {
...
// 初始化scope
scope: new EffectScope(true /* detached */),
...
}
在我們執(zhí)行setup之前,會(huì)調(diào)用setCurrentInstance,他會(huì)調(diào)用instance.scope.on,那么就會(huì)將activeEffectScope賦值為instance.scope,那么在setup中注冊(cè)的computed、watch等就都會(huì)被收集到instance.scope.effects
function setupStatefulComponent(
instance: ComponentInternalInstance,
isSSR: boolean
) {
// 組件對(duì)象
const Component = instance.type as ComponentOptions
...
// 2. call setup()
const { setup } = Component
if (setup) {
// 創(chuàng)建setupContext
const setupContext = (instance.setupContext =
// setup參數(shù)個(gè)數(shù)判斷 大于一個(gè)參數(shù)創(chuàng)建setupContext
setup.length > 1 ? createSetupContext(instance) : null)
// instance賦值給currentInstance
// 設(shè)置當(dāng)前實(shí)例為instance 為了在setup中可以通過(guò)getCurrentInstance獲取到當(dāng)前實(shí)例
// 同時(shí)開(kāi)啟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]
)
// 重新開(kāi)啟副作用收集
resetTracking()
// currentInstance置為空
// activeEffectScope賦值為instance.scope.parent
// 同時(shí)instance.scope.off()
unsetCurrentInstance()
...
} else {
finishComponentSetup(instance, isSSR)
}
}
對(duì)于選項(xiàng)式API的收集是同樣的操作:
// support for 2.x options
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
setCurrentInstance(instance)
pauseTracking()
// 處理options API
applyOptions(instance)
resetTracking()
unsetCurrentInstance()
}
完成了收集那么對(duì)于清理就只需要在組件卸載的時(shí)候執(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及實(shí)現(xiàn)原理的詳細(xì)內(nèi)容,更多關(guān)于Vue3 effectScope API的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
集合框架(Collections Framework)詳解及代碼示例
這篇文章主要介紹了集合框架(Collections Framework)詳解及代碼示例,文章涉及集合數(shù)組的區(qū)別,collection接口,iterator迭代器,list接口及其用法,LinkedHashSet集合等有關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11
SpringBoot實(shí)現(xiàn)釘釘機(jī)器人消息推送的示例代碼
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)釘釘機(jī)器人消息推送的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03
Java Filter 過(guò)濾器詳細(xì)介紹及實(shí)例代碼
Filter也稱(chēng)之為過(guò)濾器,它是Servlet技術(shù)中最實(shí)用的技術(shù),本文章WEB開(kāi)發(fā)人員通過(guò)Filter技術(shù),對(duì)web服務(wù)器管理的所有web資源進(jìn)行攔截,從而實(shí)現(xiàn)一些特殊的功能,本文章將向大家介紹Java 中的 Filter 過(guò)濾器,需要的朋友可以參考一下2016-12-12
java搜索無(wú)向圖中兩點(diǎn)之間所有路徑的算法
這篇文章主要介紹了java搜索無(wú)向圖中兩點(diǎn)之間所有路徑的算法2019-01-01
Spring Cloud Gateway替代zuul作為API網(wǎng)關(guān)的方法
本文簡(jiǎn)要介紹如何使用Spring Cloud Gateway 作為API 網(wǎng)關(guān)(不是使用zuul作為網(wǎng)關(guān)),結(jié)合實(shí)例代碼給大家詳細(xì)講解,感興趣的朋友跟隨小編一起看看吧2023-02-02
詳解FileInputStream讀取文件數(shù)據(jù)的兩種方式
這篇文章主要介紹了詳解FileInputStream讀取文件數(shù)據(jù)的兩種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08
SpringBoot的攔截器中依賴(lài)注入為null的解決方法
這篇文章主要介紹了SpringBoot的攔截器中依賴(lài)注入為null的解決方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06
springboot如何獲取application.yml里值的方法
這篇文章主要介紹了springboot如何獲取application.yml里的值,文章圍繞主題相關(guān)自資料展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-04-04

