共计 5053 个字符,预计需要花费 13 分钟才能阅读完成。
前言
参考代码版本: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 = false
getCurrentScope
getCurrentScope
能够获取以后处于沉闷状态的 EffectScope
。这里处于沉闷状态的EffectScope
指得是以后执行环境在所处的那个EffectScope
。
export function getCurrentScope() {return activeEffectScope}