前言
參考代碼版本:vue 3.2.37
官方文檔:https://vuejs.org/
關于為什么要有effectScope
可以參考RFC
使用示例
effectScope
可以對內部的響應式對象的副作用effect
進行統(tǒng)一管理。
const counter = ref(1)
const scope = effectScope()
scope.run(() => {
const doubled = computed(() => counter.value * 2)
watch(doubled, () => console.log(doubled.value))
watchEffect(() => console.log('Count: ', doubled.value))
})
// 處理掉當前作用域內的所有 effect
scope.stop()
effectScope
接收一個boolean
值,如果傳true
代表游離模式萝勤,那么創(chuàng)建的scope
不會被父scope
收集,通俗來講呐伞,如果是游離模式敌卓,那么scope
之間是不存在父子關系的,每一個scope
都是獨立的伶氢。
export function effectScope(detached?: boolean) {
return new EffectScope(detached)
}
effectScope
返回一個EffectScope
實例趟径。
EffectScope
export class EffectScope {
active = true
effects: ReactiveEffect[] = []
cleanups: (() => void)[] = []
parent: EffectScope | undefined
scopes: EffectScope[] | undefined
/**
* track a child scope's index in its parent's scopes array for optimized
* removal
*/
private index: number | undefined
constructor(detached = false) {
if (!detached && activeEffectScope) {
this.parent = activeEffectScope
this.index =
(activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
this
) - 1
}
}
run<T>(fn: () => T): T | undefined {
if (this.active) {
try {
activeEffectScope = this
return fn()
} finally {
activeEffectScope = this.parent
}
} else if (__DEV__) {
warn(`cannot run an inactive effect scope.`)
}
}
on() {
activeEffectScope = this
}
off() {
activeEffectScope = this.parent
}
stop(fromParent?: boolean) {
if (this.active) {
let i, l
for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].stop()
}
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.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.active = false
}
}
}
constructor
EffectScope
構造器接收一個參數(shù):detached
,默認值為false
癣防,代表EffectScope
是否是游離狀態(tài)蜗巧。
constructor(detached = false) {
if (!detached && activeEffectScope) {
this.parent = activeEffectScope
this.index =
(activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
this
) - 1
}
}
如果detached
為false
,并且存在activeEffectScope
(activeEffectScope
是個全局變量)的情況蕾盯,會將activeEffectScope
賦值給this.parent
幕屹,同時會將當前EffectScope
實例放入activeEffectScope.scopes
中,并將activeEffectScope.scopes
最后一個索引賦值給當前EffectScope
實例的index
屬性。這樣就可以通過this.index
來獲取EffectScope
實例在父scope
中的索引位置望拖。
run
run
方法可以接收一個函數(shù)參數(shù)渺尘。
run<T>(fn: () => T): T | undefined {
if (this.active) {
try {
activeEffectScope = this
return fn()
} finally {
activeEffectScope = this.parent
}
} else if (__DEV__) {
warn(`cannot run an inactive effect scope.`)
}
}
run
方法會首先對this.active
進行判斷,如果this.active
為true
说敏,也就是EffectScope
處于激活狀態(tài)鸥跟,那么會將this
賦給activeEffectScope
,然后執(zhí)行fn
盔沫,并返回其執(zhí)行結果锌雀。當fn
執(zhí)行完畢后,將activeEffectScope
改為this.parent
迅诬。
on
on() {
activeEffectScope = this
}
on
方法會將activeEffectScope
指向當前EffectScope
實例腋逆。
off
off() {
activeEffectScope = this.parent
}
off
方法會將activeEffectScope
指向當前EffectScope
實例的父scope
。
stop
stop
函數(shù)的作用是清除scope
內的所有的響應式效果侈贷,包括子scope
惩歉。stop
接收一個boolean
類型的fromParent
參數(shù),如果fromParent
為true
俏蛮,stop
將不會刪除在父scope
中的引用撑蚌。
stop(fromParent?: boolean) {
if (this.active) {
let i, l
// 調用ReactiveEffect.prototype.stop,清除scope內所有響應式效果
for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].stop()
}
// 觸發(fā)scope銷毀時的監(jiān)聽函數(shù)
for (i = 0, l = this.cleanups.length; i < l; i++) {
this.cleanups[i]()
}
// 銷毀子scope
if (this.scopes) {
for (i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].stop(true)
}
}
// 嵌套范圍搏屑,從父級取消引用以避免內存泄漏
if (this.parent && !fromParent) {
// 獲取父scope的中最后一個scope
const last = this.parent.scopes!.pop()
// last不是當前的scope
if (last && last !== this) {
// 將last放在當前scope在parent.scopes中的索引位置
this.parent.scopes![this.index!] = last
// last.index改為this.index
last.index = this.index!
}
}
// 修改scope的激活狀態(tài)
this.active = false
}
}
stop
中的所有操作都要建立在scope
處于激活狀態(tài)的基礎上争涌。首先遍歷this.effects
執(zhí)行元素的stop
方法。
for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].stop()
}
scope.effects
存儲的是在run
過程中獲取到的ReactiveEffect
實例辣恋,這些ReactiveEffect
實例會通過一個recordEffectScope
方法被添加到scope.effects
中亮垫。
export function recordEffectScope(
effect: ReactiveEffect,
scope: EffectScope | undefined = activeEffectScope
) {
if (scope && scope.active) {
scope.effects.push(effect)
}
}
當遍歷完scope.effects
或,會遍歷scope.cleanups
屬性伟骨。
for (i = 0, l = this.cleanups.length; i < l; i++) {
this.cleanups[i]()
}
scope.cleanups
中保存的是通過onScopeDispose
添加的scope
銷毀監(jiān)聽函數(shù)饮潦。
export function onScopeDispose(fn: () => void) {
if (activeEffectScope) {
activeEffectScope.cleanups.push(fn)
} else if (__DEV__) {
warn(
`onScopeDispose() is called when there is no active effect scope` +
` to be associated with.`
)
}
}
如果當前scope
存在scopes
屬性,意味著當前scope
存在子scope
携狭,所以需要將所有子scope
也進行銷毀继蜡。
if (this.scopes) {
for (i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].stop(true)
}
}
如果當前scope
存在parent
的話,需要將scope
從其parent
中移除逛腿。
if (this.parent && !fromParent) {
// 獲取父scope的中最后一個scope
const last = this.parent.scopes!.pop()
// last不是當前的scope
if (last && last !== this) {
// 將last放在當前scope在parent.scopes中的索引位置
this.parent.scopes![this.index!] = last
// last.index改為this.index
last.index = this.index!
}
}
這里的移除過邏輯是稀并,先獲取當前scope
的父scope
中的所有子scope
,然后取出最后一個scope
单默,這里用last
代表(注意last
不一定和當前scope
相同)碘举,如果last
和當前scope
不同的話,需要讓last
替換當前scope
雕凹,這樣我們就把當前scope
從其父scope
中移除了殴俱。這里僅僅替換是不夠的,因為last.index
此時還是之前父scope
的最后一個索引枚抵,所以還需要把last.index
改為當前scope
在其父scope.scopes
中的位置线欲。這樣就完全移除了scope
。
最后汽摹,需要把scope
的激活狀態(tài)改為false
李丰。
this.active = false
getCurrentScope
getCurrentScope
可以獲取當前處于活躍狀態(tài)的EffectScope
。這里處于活躍狀態(tài)的EffectScope
指得是當前執(zhí)行環(huán)境在所處的那個EffectScope
逼泣。
export function getCurrentScope() {
return activeEffectScope
}