Skip to content

Commit

Permalink
fix(reactivity): avoid exponential perf cost with deeply chained comp…
Browse files Browse the repository at this point in the history
…uteds

close #11928
  • Loading branch information
yyx990803 committed Sep 16, 2024
1 parent 8492c3c commit 3bc798e
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 18 deletions.
9 changes: 7 additions & 2 deletions packages/reactivity/src/computed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
EffectFlags,
type Subscriber,
activeSub,
batch,
refreshComputed,
} from './effect'
import type { Ref } from './ref'
Expand Down Expand Up @@ -111,8 +112,12 @@ export class ComputedRefImpl<T = any> implements Subscriber {
*/
notify(): void {
this.flags |= EffectFlags.DIRTY
// avoid infinite self recursion
if (activeSub !== this) {
if (
!(this.flags & EffectFlags.NOTIFIED) &&
// avoid infinite self recursion
activeSub !== this
) {
batch(this)
this.dep.notify()
} else if (__DEV__) {
// TODO warn
Expand Down
6 changes: 1 addition & 5 deletions packages/reactivity/src/dep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,7 @@ export class Dep {
// original order at the end of the batch, but onTrigger hooks should
// be invoked in original order here.
for (let head = this.subsHead; head; head = head.nextSub) {
if (
__DEV__ &&
head.sub.onTrigger &&
!(head.sub.flags & EffectFlags.NOTIFIED)
) {
if (head.sub.onTrigger && !(head.sub.flags & EffectFlags.NOTIFIED)) {
head.sub.onTrigger(
extend(
{
Expand Down
35 changes: 24 additions & 11 deletions packages/reactivity/src/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export interface ReactiveEffectRunner<T = any> {
export let activeSub: Subscriber | undefined

export enum EffectFlags {
/**
* ReactiveEffect only
*/
ACTIVE = 1 << 0,
RUNNING = 1 << 1,
TRACKING = 1 << 2,
Expand Down Expand Up @@ -66,6 +69,10 @@ export interface Subscriber extends DebuggerOptions {
* @internal
*/
flags: EffectFlags
/**
* @internal
*/
next?: Subscriber
/**
* @internal
*/
Expand All @@ -92,7 +99,7 @@ export class ReactiveEffect<T = any>
/**
* @internal
*/
nextEffect?: ReactiveEffect = undefined
next?: Subscriber = undefined
/**
* @internal
*/
Expand Down Expand Up @@ -134,9 +141,7 @@ export class ReactiveEffect<T = any>
return
}
if (!(this.flags & EffectFlags.NOTIFIED)) {
this.flags |= EffectFlags.NOTIFIED
this.nextEffect = batchedEffect
batchedEffect = this
batch(this)
}
}

Expand Down Expand Up @@ -226,7 +231,13 @@ export class ReactiveEffect<T = any>
// }

let batchDepth = 0
let batchedEffect: ReactiveEffect | undefined
let batchedSub: Subscriber | undefined

export function batch(sub: Subscriber): void {
sub.flags |= EffectFlags.NOTIFIED
sub.next = batchedSub
batchedSub = sub
}

/**
* @internal
Expand All @@ -245,16 +256,17 @@ export function endBatch(): void {
}

let error: unknown
while (batchedEffect) {
let e: ReactiveEffect | undefined = batchedEffect
batchedEffect = undefined
while (batchedSub) {
let e: Subscriber | undefined = batchedSub
batchedSub = undefined
while (e) {
const next: ReactiveEffect | undefined = e.nextEffect
e.nextEffect = undefined
const next: Subscriber | undefined = e.next
e.next = undefined
e.flags &= ~EffectFlags.NOTIFIED
if (e.flags & EffectFlags.ACTIVE) {
try {
e.trigger()
// ACTIVE flag is effect-only
;(e as ReactiveEffect).trigger()
} catch (err) {
if (!error) error = err
}
Expand Down Expand Up @@ -331,6 +343,7 @@ function isDirty(sub: Subscriber): boolean {
* @internal
*/
export function refreshComputed(computed: ComputedRefImpl): undefined {
computed.flags &= ~EffectFlags.NOTIFIED
if (
computed.flags & EffectFlags.TRACKING &&
!(computed.flags & EffectFlags.DIRTY)
Expand Down

0 comments on commit 3bc798e

Please sign in to comment.