前言
参考代码版本:vue 3.2.37
官网文档:https://vuejs.org/
对于为什么要有effectScope能够参考RFC
应用示例
effectScope能够对外部的响应式对象的副作用effect进行对立治理。
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))})// 解决掉以后作用域内的所有 effectscope.stop()effectScope接管一个boolean值,如果传true代表游离模式,那么创立的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结构器接管一个参数:detached,默认值为false,代表EffectScope是否是游离状态。
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办法能够接管一个函数参数。
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处于激活状态,那么会将this赋给activeEffectScope,而后执行fn,并返回其执行后果。当fn执行结束后,将activeEffectScope改为this.parent。
on
on() { activeEffectScope = this}on办法会将activeEffectScope指向以后EffectScope实例。
off
off() { activeEffectScope = this.parent}off办法会将activeEffectScope指向以后EffectScope实例的父scope。
stop
stop函数的作用是革除scope内的所有的响应式成果,包含子scope。stop接管一个boolean类型的fromParent参数,如果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() } // 触发scope销毁时的监听函数 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的激活状态 this.active = false }}stop中的所有操作都要建设在scope处于激活状态的根底上。首先遍历this.effects执行元素的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销毁监听函数。
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的激活状态改为false。
this.active = falsegetCurrentScope
getCurrentScope能够获取以后处于沉闷状态的EffectScope。这里处于沉闷状态的EffectScope指得是以后执行环境在所处的那个EffectScope。
export function getCurrentScope() { return activeEffectScope}