概述
EffectScope
是Vue3中一个响应式系统的辅助类,用于管理副作用(effect
)的作用域。它可以帮助我们更好地组织和管理多个effect
,便于一起停止或暂停以及恢复,避免了全局状态的污染和管理的复杂性。
每一个vue组件的实例上都会挂载一个EffectScope
的实例scope
,该挂载动作会在createComponentInstance
中进行。在组件被激活挂载时,会调用scope.on()
方法,将当前作用域设置为活动作用域;而在组件被卸载或者销毁时,会调用scope.stop()
方法。
源码解析
let activeEffectScope;
class EffectScope {
constructor(detached = false) {
this.detached = detached; // 表示该作用域是否独立(不嵌套在父作用域中)
this._active = true; //表示作用域是否激活
this._on = 0; // 一个计数器,用于记录当前作用域被开启的次数(通过on方法)
this.effects = []; // 存储属于该作用域的effect
this.cleanups = []; // 存储清理函数,当作用域停止时会执行这些清理函数
this._isPaused = false; // 表示作用域是否被暂停
this.parent = undefined; //指向父作用域
this.scopes = undefined; //存储嵌套的子作用域
this.index = undefined; // 在父作用域的scopes数组中的索引
this.prevScope = undefined; // 在调用 on 方法时,用于保存当前的activeEffectScope,以便在off时恢复,上一个作用域(用于作用域链)
this.parent = activeEffectScope; //将父作用域指向激活的作用域
if (!detached && activeEffectScope) {
// 若该作用域不独立且当前存在激活的作用域,则将该作用域存放到激活作用域(父作用域)的子作用域的末尾,并设置该作用域的索引
this.index = (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
this
) - 1;
}
}
// 暂停作用域
pause() {
if (this._active) {
this._isPaused = true;
let i, l;
if (this.scopes) {
for (i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].pause();
}
}
for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].pause();
}
}
}
// 恢复作用域
resume() {
if (this._active) {
if (this._isPaused) {
this._isPaused = false;
let i, l;
if (this.scopes) {
for (i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].resume();
}
}
for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].resume();
}
}
}
}
// 在作用域内运行函数
run(fn) {
if (this._active) {
const currentEffectScope = activeEffectScope;
try {
activeEffectScope = this;
return fn();
} finally {
activeEffectScope = currentEffectScope;
}
}
}
// 开启作用域
on() {
if (++this._on === 1) {
this.prevScope = activeEffectScope;
activeEffectScope = this;
}
}
// 关闭作用域
off() {
if (this._on > 0 && --this._on === 0) {
activeEffectScope = this.prevScope;
this.prevScope = void 0;
}
}
// 停止作用域
stop(fromParent) {
if (this._active) {
this._active = false;
let i, l;
for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].stop();
}
this.effects.length = 0;
for (i = 0, l = this.cleanups.length; i < l; i++) {
this.cleanups[i]();
}
this.cleanups.length = 0;
if (this.scopes) {
for (i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].stop(true);
}
this.scopes.length = 0;
}
if (!this.detached && this.parent && !fromParent) {
const last = this.parent.scopes.pop();
if (last && last !== this) {
this.parent.scopes[this.index] = last;
last.index = this.index;
}
}
this.parent = void 0;
}
}
}
核心方法
核心方法主要分为三类:作用域控制(pause
/resume
/stop
)、作用域运行(run
)和作用域激活(on
/off
)
作用域控制
pause
pause
方法就是用于暂停作用域及其所有子作用域和副作用。先是判断作用域是否处于激活状态,若是激活状态,则修改作用域的暂停状态this._isPaused
,改为true
,然后遍历子作用域和副作用数组,调用其stop
方法实现暂停。
resume
resume
方法用于暂停状态的作用域及其所有子作用域和副作用。和pause
方法是相反的操作,会修改this.isPaused
为false
,遍历调用所有子作用域和副作用的resume
方法。
stop
stop
方法用于停止作用域:清理所有副作用和子作用域,并且从父作用域中移除自身。
stop
方法会停止所有effect
,执行所有cleanups
,并递归停止所有子作用域。如果该作用域不是独立的且由副作用域,并且不是由副作用域触发的停止,则从副作用域的scopes
数组中移除自己,并调整数组。
作用域运行
run
run
方法用于在该作用域内运行函数,这样函数内创建的effect
会自动注册到该作用域中。主要就是将该作用域切换为激活作用域,然后执行fn
,最后恢复激活作用域为之前的。
作用域激活
on
on
方法用于激活作用域,就是增加计数器,若计数器增加后等于 1,则将该作用域作为激活作用域。
off
off
方法和on
方法相对,用于关闭作用域,减少计数器,若计数器大于 0,且减少后等于0,则恢复激活作用域为前值。
通过on
/off
方法,可以临时切换当前激活的作用域,这样可以实现在on
和off
切换之间创建effect
会注册到该作用域中。
辅助方法
和EffectScope
类相关的三个辅助方法:effectScope
、getCurrentScope
和onScopeDispose
。
effectScope
effectScope
方法就是返回EffectScope
的实例对象。
function effectScope(detached) {
return new EffectScope(detached);
}
getCurrentScope
getCurrentScope
方法用于获取当前活跃(激活)的作用域
function getCurrentScope() {
return activeEffectScope;
}
onScopeDispose
onScopeDispose
方法会判断若当前作用域存在,则将函数fn
注入到作用域的清理数组cleanups
中。
function onScopeDispose(fn, failSilently = false) {
if (activeEffectScope) {
activeEffectScope.cleanups.push(fn);
}
}