mirror of
https://github.com/vuejs/core.git
synced 2024-11-22 04:51:10 +00:00
fix(reactivity): avoid exponential perf cost and reduce call stack depth for deeply chained computeds (#11944)
close #11928
This commit is contained in:
parent
cbc39d54f0
commit
c74bb8c2dd
@ -5,6 +5,7 @@ import {
|
||||
EffectFlags,
|
||||
type Subscriber,
|
||||
activeSub,
|
||||
batch,
|
||||
refreshComputed,
|
||||
} from './effect'
|
||||
import type { Ref } from './ref'
|
||||
@ -109,11 +110,15 @@ export class ComputedRefImpl<T = any> implements Subscriber {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
notify(): void {
|
||||
notify(): true | void {
|
||||
this.flags |= EffectFlags.DIRTY
|
||||
// avoid infinite self recursion
|
||||
if (activeSub !== this) {
|
||||
this.dep.notify()
|
||||
if (
|
||||
!(this.flags & EffectFlags.NOTIFIED) &&
|
||||
// avoid infinite self recursion
|
||||
activeSub !== this
|
||||
) {
|
||||
batch(this)
|
||||
return true
|
||||
} else if (__DEV__) {
|
||||
// TODO warn
|
||||
}
|
||||
|
@ -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(
|
||||
{
|
||||
@ -180,7 +176,12 @@ export class Dep {
|
||||
}
|
||||
}
|
||||
for (let link = this.subs; link; link = link.prevSub) {
|
||||
link.sub.notify()
|
||||
if (link.sub.notify()) {
|
||||
// if notify() returns `true`, this is a computed. Also call notify
|
||||
// on its dep - it's called here instead of inside computed's notify
|
||||
// in order to reduce call stack depth.
|
||||
;(link.sub as ComputedRefImpl).dep.notify()
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
endBatch()
|
||||
|
@ -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,
|
||||
@ -69,7 +72,13 @@ export interface Subscriber extends DebuggerOptions {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
notify(): void
|
||||
next?: Subscriber
|
||||
/**
|
||||
* returning `true` indicates it's a computed that needs to call notify
|
||||
* on its dep too
|
||||
* @internal
|
||||
*/
|
||||
notify(): true | void
|
||||
}
|
||||
|
||||
const pausedQueueEffects = new WeakSet<ReactiveEffect>()
|
||||
@ -92,7 +101,7 @@ export class ReactiveEffect<T = any>
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
nextEffect?: ReactiveEffect = undefined
|
||||
next?: Subscriber = undefined
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@ -134,9 +143,7 @@ export class ReactiveEffect<T = any>
|
||||
return
|
||||
}
|
||||
if (!(this.flags & EffectFlags.NOTIFIED)) {
|
||||
this.flags |= EffectFlags.NOTIFIED
|
||||
this.nextEffect = batchedEffect
|
||||
batchedEffect = this
|
||||
batch(this)
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,7 +233,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
|
||||
@ -245,16 +258,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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user