【vue3源码】三、effectScope源码解析
前言
参考代码版本: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))
})
// 处理掉当前作用域内的所有 effect
scope.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
}转载自:https://segmentfault.com/a/1190000042055472