})
})
- expect(scope.effects.length).toBe(2)
- expect(scope.effects[1]).toBeInstanceOf(EffectScope)
+ expect(scope.effects.length).toBe(1)
+ expect(scope.scopes.length).toBe(1)
+ expect(scope.scopes[0]).toBeInstanceOf(EffectScope)
expect(dummy).toBe(0)
counter.num = 7
it('should derefence child scope from parent scope after stopping child scope (no memleaks)', async () => {
const parent = new EffectScope()
const child = parent.run(() => new EffectScope())!
- expect(parent.effects.includes(child)).toBe(true)
+ expect(parent.scopes.includes(child)).toBe(true)
child.stop()
- expect(parent.effects.includes(child)).toBe(false)
+ expect(parent.scopes.includes(child)).toBe(false)
})
it('test with higher level APIs', async () => {
-import { remove } from '@vue/shared'
import { ReactiveEffect } from './effect'
import { warn } from './warning'
export class EffectScope {
active = true
- effects: (ReactiveEffect | EffectScope)[] = []
+ effects: ReactiveEffect[] = []
cleanups: (() => void)[] = []
parent: EffectScope | undefined
+ private children: EffectScope[] | undefined
+ private parentIndex: number | undefined
constructor(detached = false) {
if (!detached) {
- recordEffectScope(this)
- this.parent = activeEffectScope
+ this.recordEffectScope()
}
}
+ get scopes(): EffectScope[] {
+ if (!this.children) this.children = []
+ return this.children
+ }
+
run<T>(fn: () => T): T | undefined {
if (this.active) {
try {
}
}
- stop(fromParent = false) {
+ stop() {
if (this.active) {
- this.effects.forEach(e => e.stop(true))
+ this.effects.forEach(e => e.stop())
+ this.children?.forEach(e => e.stop())
this.cleanups.forEach(cleanup => cleanup())
+ this.parent?.derefChildScope(this)
this.active = false
- if (!fromParent && this.parent) {
- remove(this.parent.effects, this)
- }
+ }
+ }
+
+ private recordEffectScope() {
+ const parent = activeEffectScope
+ if (parent && parent.active) {
+ this.parent = parent
+ this.parentIndex = parent.scopes.push(this) - 1
+ }
+ }
+
+ private derefChildScope(scope: EffectScope) {
+ // reuse the freed index by moving the last array entry
+ const last = this.scopes.pop()
+ if (last && last !== scope) {
+ const childIndex = scope.parentIndex!
+ this.scopes[childIndex] = last
+ last.parentIndex = childIndex
}
}
}
}
export function recordEffectScope(
- effect: ReactiveEffect | EffectScope,
+ effect: ReactiveEffect,
scope?: EffectScope | null
) {
scope = scope || activeEffectScope