概述
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 = []; // 存儲屬于該作用域的effectthis.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);}
}