Skip to content

Commit 8e2d140

Browse files
committed
refactor(reactivity): fix cleanup performance regression
1 parent 7a161f1 commit 8e2d140

File tree

4 files changed

+43
-51
lines changed

4 files changed

+43
-51
lines changed

packages/reactivity/__tests__/effectScope.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ describe('reactivity/effect/scope', () => {
361361
expect(cleanupCalls).toBe(1)
362362

363363
expect(getEffectsCount(scope)).toBe(0)
364-
expect(scope.cleanups).toBe(0)
364+
expect(scope.cleanupsLength).toBe(0)
365365
})
366366
})
367367

packages/reactivity/src/effect.ts

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,20 @@ export class ReactiveEffect<T = any>
6161
deps: Link | undefined = undefined
6262
depsTail: Link | undefined = undefined
6363
flags: number = SubscriberFlags.Dirty
64-
cleanups: number = 0
6564

6665
// Dependency
6766
subs: Link | undefined = undefined
6867
subsTail: Link | undefined = undefined
6968

69+
/**
70+
* @internal
71+
*/
72+
cleanups: (() => void)[] = []
73+
/**
74+
* @internal
75+
*/
76+
cleanupsLength = 0
77+
7078
// dev only
7179
onTrack?: (event: DebuggerEvent) => void
7280
// dev only
@@ -85,7 +93,7 @@ export class ReactiveEffect<T = any>
8593
}
8694

8795
get active(): boolean {
88-
return this.deps !== undefined
96+
return !!this.flags || this.deps !== undefined
8997
}
9098

9199
pause(): void {
@@ -121,10 +129,7 @@ export class ReactiveEffect<T = any>
121129
}
122130

123131
run(): T {
124-
const cleanups = this.cleanups
125-
if (cleanups) {
126-
cleanup(this, cleanups)
127-
}
132+
cleanup(this)
128133
const prevSub = activeSub
129134
setActiveSub(this)
130135
startTracking(this)
@@ -153,15 +158,13 @@ export class ReactiveEffect<T = any>
153158

154159
stop(): void {
155160
const sub = this.subs
156-
const cleanups = this.cleanups
157161
if (sub !== undefined) {
158162
unlink(sub)
159163
}
160164
startTracking(this)
161165
endTracking(this)
162-
if (cleanups) {
163-
cleanup(this, cleanups)
164-
}
166+
cleanup(this)
167+
this.flags = 0
165168
}
166169

167170
get dirty(): boolean {
@@ -280,30 +283,16 @@ export function resetTracking(): void {
280283
}
281284
}
282285

283-
const cleanupCbs = new WeakMap()
284-
285-
export function onCleanup(
286-
sub: Subscriber & { cleanups: number },
287-
cb: () => void,
288-
): void {
289-
const cbs = cleanupCbs.get(sub)
290-
if (cbs === undefined) {
291-
cleanupCbs.set(sub, [cb])
292-
sub.cleanups = 1
293-
} else {
294-
cbs[sub.cleanups!++] = cb
295-
}
296-
}
297-
298286
export function cleanup(
299-
sub: Subscriber & { cleanups: number },
300-
length: number,
287+
sub: Subscriber & { cleanups: (() => void)[]; cleanupsLength: number },
301288
): void {
302-
const cbs = cleanupCbs.get(sub)!
303-
for (let i = 0; i < length; ++i) {
304-
cbs[i]()
289+
const l = sub.cleanupsLength
290+
if (l) {
291+
for (let i = 0; i < l; i++) {
292+
sub.cleanups[i]()
293+
}
294+
sub.cleanupsLength = 0
305295
}
306-
sub.cleanups = 0
307296
}
308297

309298
/**
@@ -319,9 +308,8 @@ export function cleanup(
319308
* an active effect.
320309
*/
321310
export function onEffectCleanup(fn: () => void, failSilently = false): void {
322-
const e = activeSub
323-
if (e instanceof ReactiveEffect) {
324-
onCleanup(e, () => cleanupEffect(fn))
311+
if (activeSub instanceof ReactiveEffect) {
312+
activeSub.cleanups[activeSub.cleanupsLength++] = () => cleanupEffect(fn)
325313
} else if (__DEV__ && !failSilently) {
326314
warn(
327315
`onEffectCleanup() was called when there was no active effect` +

packages/reactivity/src/effectScope.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EffectFlags, cleanup, onCleanup } from './effect'
1+
import { EffectFlags, cleanup } from './effect'
22
import {
33
type Dependency,
44
type Link,
@@ -17,20 +17,28 @@ export class EffectScope implements Subscriber, Dependency {
1717
deps: Link | undefined = undefined
1818
depsTail: Link | undefined = undefined
1919
flags: number = 0
20-
cleanups: number = 0
2120

2221
// Dependency
2322
subs: Link | undefined = undefined
2423
subsTail: Link | undefined = undefined
2524

25+
/**
26+
* @internal
27+
*/
28+
cleanups: (() => void)[] = []
29+
/**
30+
* @internal
31+
*/
32+
cleanupsLength = 0
33+
2634
constructor(detached = false) {
2735
if (!detached && activeEffectScope) {
2836
link(this, activeEffectScope)
2937
}
3038
}
3139

3240
get active(): boolean {
33-
return this.deps !== undefined
41+
return !!this.flags || this.deps !== undefined
3442
}
3543

3644
notify(): void {}
@@ -75,15 +83,13 @@ export class EffectScope implements Subscriber, Dependency {
7583

7684
stop(): void {
7785
const sub = this.subs
78-
const cleanups = this.cleanups
7986
if (sub !== undefined) {
8087
unlink(sub)
8188
}
8289
startTracking(this)
8390
endTracking(this)
84-
if (cleanups) {
85-
cleanup(this, cleanups)
86-
}
91+
cleanup(this)
92+
this.flags = 0
8793
}
8894
}
8995

@@ -126,7 +132,7 @@ export function setCurrentScope(
126132
*/
127133
export function onScopeDispose(fn: () => void, failSilently = false): void {
128134
if (activeEffectScope !== undefined) {
129-
onCleanup(activeEffectScope, fn)
135+
activeEffectScope.cleanups[activeEffectScope.cleanupsLength++] = fn
130136
} else if (__DEV__ && !failSilently) {
131137
warn(
132138
`onScopeDispose() is called when there is no active effect scope` +

packages/reactivity/src/watch.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import {
1515
type DebuggerOptions,
1616
ReactiveEffect,
1717
cleanup,
18-
onCleanup,
1918
pauseTracking,
2019
resetTracking,
2120
} from './effect'
@@ -98,9 +97,10 @@ export function onWatcherCleanup(
9897
if (owner) {
9998
const { call } = owner.options
10099
if (call) {
101-
onCleanup(owner, () => call(cleanupFn, WatchErrorCodes.WATCH_CLEANUP))
100+
owner.cleanups[owner.cleanupsLength++] = () =>
101+
call(cleanupFn, WatchErrorCodes.WATCH_CLEANUP)
102102
} else {
103-
onCleanup(owner, cleanupFn)
103+
owner.cleanups[owner.cleanupsLength++] = cleanupFn
104104
}
105105
} else if (__DEV__ && !failSilently) {
106106
warn(
@@ -158,10 +158,10 @@ export class WatcherEffect extends ReactiveEffect {
158158
} else {
159159
// no cb -> simple effect
160160
getter = () => {
161-
if (this.cleanups) {
161+
if (this.cleanupsLength) {
162162
pauseTracking()
163163
try {
164-
cleanup(this, this.cleanups)
164+
cleanup(this)
165165
} finally {
166166
resetTracking()
167167
}
@@ -231,9 +231,7 @@ export class WatcherEffect extends ReactiveEffect {
231231
: hasChanged(newValue, this.oldValue))
232232
) {
233233
// cleanup before running cb again
234-
if (this.cleanups) {
235-
cleanup(this, this.cleanups)
236-
}
234+
cleanup(this)
237235
const currentWatcher = activeWatcher
238236
activeWatcher = this
239237
try {

0 commit comments

Comments
 (0)