Skip to content

Commit 3bc798e

Browse files
committed
fix(reactivity): avoid exponential perf cost with deeply chained computeds
close #11928
1 parent 8492c3c commit 3bc798e

File tree

3 files changed

+32
-18
lines changed

3 files changed

+32
-18
lines changed

packages/reactivity/src/computed.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
EffectFlags,
66
type Subscriber,
77
activeSub,
8+
batch,
89
refreshComputed,
910
} from './effect'
1011
import type { Ref } from './ref'
@@ -111,8 +112,12 @@ export class ComputedRefImpl<T = any> implements Subscriber {
111112
*/
112113
notify(): void {
113114
this.flags |= EffectFlags.DIRTY
114-
// avoid infinite self recursion
115-
if (activeSub !== this) {
115+
if (
116+
!(this.flags & EffectFlags.NOTIFIED) &&
117+
// avoid infinite self recursion
118+
activeSub !== this
119+
) {
120+
batch(this)
116121
this.dep.notify()
117122
} else if (__DEV__) {
118123
// TODO warn

packages/reactivity/src/dep.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,7 @@ export class Dep {
163163
// original order at the end of the batch, but onTrigger hooks should
164164
// be invoked in original order here.
165165
for (let head = this.subsHead; head; head = head.nextSub) {
166-
if (
167-
__DEV__ &&
168-
head.sub.onTrigger &&
169-
!(head.sub.flags & EffectFlags.NOTIFIED)
170-
) {
166+
if (head.sub.onTrigger && !(head.sub.flags & EffectFlags.NOTIFIED)) {
171167
head.sub.onTrigger(
172168
extend(
173169
{

packages/reactivity/src/effect.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ export interface ReactiveEffectRunner<T = any> {
3939
export let activeSub: Subscriber | undefined
4040

4141
export enum EffectFlags {
42+
/**
43+
* ReactiveEffect only
44+
*/
4245
ACTIVE = 1 << 0,
4346
RUNNING = 1 << 1,
4447
TRACKING = 1 << 2,
@@ -66,6 +69,10 @@ export interface Subscriber extends DebuggerOptions {
6669
* @internal
6770
*/
6871
flags: EffectFlags
72+
/**
73+
* @internal
74+
*/
75+
next?: Subscriber
6976
/**
7077
* @internal
7178
*/
@@ -92,7 +99,7 @@ export class ReactiveEffect<T = any>
9299
/**
93100
* @internal
94101
*/
95-
nextEffect?: ReactiveEffect = undefined
102+
next?: Subscriber = undefined
96103
/**
97104
* @internal
98105
*/
@@ -134,9 +141,7 @@ export class ReactiveEffect<T = any>
134141
return
135142
}
136143
if (!(this.flags & EffectFlags.NOTIFIED)) {
137-
this.flags |= EffectFlags.NOTIFIED
138-
this.nextEffect = batchedEffect
139-
batchedEffect = this
144+
batch(this)
140145
}
141146
}
142147

@@ -226,7 +231,13 @@ export class ReactiveEffect<T = any>
226231
// }
227232

228233
let batchDepth = 0
229-
let batchedEffect: ReactiveEffect | undefined
234+
let batchedSub: Subscriber | undefined
235+
236+
export function batch(sub: Subscriber): void {
237+
sub.flags |= EffectFlags.NOTIFIED
238+
sub.next = batchedSub
239+
batchedSub = sub
240+
}
230241

231242
/**
232243
* @internal
@@ -245,16 +256,17 @@ export function endBatch(): void {
245256
}
246257

247258
let error: unknown
248-
while (batchedEffect) {
249-
let e: ReactiveEffect | undefined = batchedEffect
250-
batchedEffect = undefined
259+
while (batchedSub) {
260+
let e: Subscriber | undefined = batchedSub
261+
batchedSub = undefined
251262
while (e) {
252-
const next: ReactiveEffect | undefined = e.nextEffect
253-
e.nextEffect = undefined
263+
const next: Subscriber | undefined = e.next
264+
e.next = undefined
254265
e.flags &= ~EffectFlags.NOTIFIED
255266
if (e.flags & EffectFlags.ACTIVE) {
256267
try {
257-
e.trigger()
268+
// ACTIVE flag is effect-only
269+
;(e as ReactiveEffect).trigger()
258270
} catch (err) {
259271
if (!error) error = err
260272
}
@@ -331,6 +343,7 @@ function isDirty(sub: Subscriber): boolean {
331343
* @internal
332344
*/
333345
export function refreshComputed(computed: ComputedRefImpl): undefined {
346+
computed.flags &= ~EffectFlags.NOTIFIED
334347
if (
335348
computed.flags & EffectFlags.TRACKING &&
336349
!(computed.flags & EffectFlags.DIRTY)

0 commit comments

Comments
 (0)