refactor(reactivity): optimize child effect scope dereferencing (#4184)

This commit is contained in:
Bas van Meurs 2021-07-29 16:26:24 +02:00 committed by GitHub
parent 3b38c9ae9b
commit 91f29540fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 37 additions and 14 deletions

View File

@ -77,8 +77,9 @@ describe('reactivity/effect/scope', () => {
}) })
}) })
expect(scope.effects.length).toBe(2) expect(scope.effects.length).toBe(1)
expect(scope.effects[1]).toBeInstanceOf(EffectScope) expect(scope.scopes.length).toBe(1)
expect(scope.scopes[0]).toBeInstanceOf(EffectScope)
expect(dummy).toBe(0) expect(dummy).toBe(0)
counter.num = 7 counter.num = 7
@ -194,9 +195,9 @@ describe('reactivity/effect/scope', () => {
it('should derefence child scope from parent scope after stopping child scope (no memleaks)', async () => { it('should derefence child scope from parent scope after stopping child scope (no memleaks)', async () => {
const parent = new EffectScope() const parent = new EffectScope()
const child = parent.run(() => new EffectScope())! const child = parent.run(() => new EffectScope())!
expect(parent.effects.includes(child)).toBe(true) expect(parent.scopes.includes(child)).toBe(true)
child.stop() child.stop()
expect(parent.effects.includes(child)).toBe(false) expect(parent.scopes.includes(child)).toBe(false)
}) })
it('test with higher level APIs', async () => { it('test with higher level APIs', async () => {

View File

@ -1,4 +1,3 @@
import { remove } from '@vue/shared'
import { ReactiveEffect } from './effect' import { ReactiveEffect } from './effect'
import { warn } from './warning' import { warn } from './warning'
@ -7,17 +6,23 @@ const effectScopeStack: EffectScope[] = []
export class EffectScope { export class EffectScope {
active = true active = true
effects: (ReactiveEffect | EffectScope)[] = [] effects: ReactiveEffect[] = []
cleanups: (() => void)[] = [] cleanups: (() => void)[] = []
parent: EffectScope | undefined parent: EffectScope | undefined
private children: EffectScope[] | undefined
private parentIndex: number | undefined
constructor(detached = false) { constructor(detached = false) {
if (!detached) { if (!detached) {
recordEffectScope(this) this.recordEffectScope()
this.parent = activeEffectScope
} }
} }
get scopes(): EffectScope[] {
if (!this.children) this.children = []
return this.children
}
run<T>(fn: () => T): T | undefined { run<T>(fn: () => T): T | undefined {
if (this.active) { if (this.active) {
try { try {
@ -45,14 +50,31 @@ export class EffectScope {
} }
} }
stop(fromParent = false) { stop() {
if (this.active) { 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.cleanups.forEach(cleanup => cleanup())
this.parent?.derefChildScope(this)
this.active = false 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
} }
} }
} }
@ -62,7 +84,7 @@ export function effectScope(detached?: boolean) {
} }
export function recordEffectScope( export function recordEffectScope(
effect: ReactiveEffect | EffectScope, effect: ReactiveEffect,
scope?: EffectScope | null scope?: EffectScope | null
) { ) {
scope = scope || activeEffectScope scope = scope || activeEffectScope