mirror of https://github.com/vuejs/core.git
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,
|
EffectFlags,
|
||||||
type Subscriber,
|
type Subscriber,
|
||||||
activeSub,
|
activeSub,
|
||||||
|
batch,
|
||||||
refreshComputed,
|
refreshComputed,
|
||||||
} from './effect'
|
} from './effect'
|
||||||
import type { Ref } from './ref'
|
import type { Ref } from './ref'
|
||||||
|
@ -109,11 +110,15 @@ export class ComputedRefImpl<T = any> implements Subscriber {
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
notify(): void {
|
notify(): true | void {
|
||||||
this.flags |= EffectFlags.DIRTY
|
this.flags |= EffectFlags.DIRTY
|
||||||
// avoid infinite self recursion
|
if (
|
||||||
if (activeSub !== this) {
|
!(this.flags & EffectFlags.NOTIFIED) &&
|
||||||
this.dep.notify()
|
// avoid infinite self recursion
|
||||||
|
activeSub !== this
|
||||||
|
) {
|
||||||
|
batch(this)
|
||||||
|
return true
|
||||||
} else if (__DEV__) {
|
} else if (__DEV__) {
|
||||||
// TODO warn
|
// TODO warn
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,11 +163,7 @@ export class Dep {
|
||||||
// original order at the end of the batch, but onTrigger hooks should
|
// original order at the end of the batch, but onTrigger hooks should
|
||||||
// be invoked in original order here.
|
// be invoked in original order here.
|
||||||
for (let head = this.subsHead; head; head = head.nextSub) {
|
for (let head = this.subsHead; head; head = head.nextSub) {
|
||||||
if (
|
if (head.sub.onTrigger && !(head.sub.flags & EffectFlags.NOTIFIED)) {
|
||||||
__DEV__ &&
|
|
||||||
head.sub.onTrigger &&
|
|
||||||
!(head.sub.flags & EffectFlags.NOTIFIED)
|
|
||||||
) {
|
|
||||||
head.sub.onTrigger(
|
head.sub.onTrigger(
|
||||||
extend(
|
extend(
|
||||||
{
|
{
|
||||||
|
@ -180,7 +176,12 @@ export class Dep {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let link = this.subs; link; link = link.prevSub) {
|
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 {
|
} finally {
|
||||||
endBatch()
|
endBatch()
|
||||||
|
|
|
@ -39,6 +39,9 @@ export interface ReactiveEffectRunner<T = any> {
|
||||||
export let activeSub: Subscriber | undefined
|
export let activeSub: Subscriber | undefined
|
||||||
|
|
||||||
export enum EffectFlags {
|
export enum EffectFlags {
|
||||||
|
/**
|
||||||
|
* ReactiveEffect only
|
||||||
|
*/
|
||||||
ACTIVE = 1 << 0,
|
ACTIVE = 1 << 0,
|
||||||
RUNNING = 1 << 1,
|
RUNNING = 1 << 1,
|
||||||
TRACKING = 1 << 2,
|
TRACKING = 1 << 2,
|
||||||
|
@ -69,7 +72,13 @@ export interface Subscriber extends DebuggerOptions {
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @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>()
|
const pausedQueueEffects = new WeakSet<ReactiveEffect>()
|
||||||
|
@ -92,7 +101,7 @@ export class ReactiveEffect<T = any>
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
nextEffect?: ReactiveEffect = undefined
|
next?: Subscriber = undefined
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
@ -134,9 +143,7 @@ export class ReactiveEffect<T = any>
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!(this.flags & EffectFlags.NOTIFIED)) {
|
if (!(this.flags & EffectFlags.NOTIFIED)) {
|
||||||
this.flags |= EffectFlags.NOTIFIED
|
batch(this)
|
||||||
this.nextEffect = batchedEffect
|
|
||||||
batchedEffect = this
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,7 +233,13 @@ export class ReactiveEffect<T = any>
|
||||||
// }
|
// }
|
||||||
|
|
||||||
let batchDepth = 0
|
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
|
* @internal
|
||||||
|
@ -245,16 +258,17 @@ export function endBatch(): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
let error: unknown
|
let error: unknown
|
||||||
while (batchedEffect) {
|
while (batchedSub) {
|
||||||
let e: ReactiveEffect | undefined = batchedEffect
|
let e: Subscriber | undefined = batchedSub
|
||||||
batchedEffect = undefined
|
batchedSub = undefined
|
||||||
while (e) {
|
while (e) {
|
||||||
const next: ReactiveEffect | undefined = e.nextEffect
|
const next: Subscriber | undefined = e.next
|
||||||
e.nextEffect = undefined
|
e.next = undefined
|
||||||
e.flags &= ~EffectFlags.NOTIFIED
|
e.flags &= ~EffectFlags.NOTIFIED
|
||||||
if (e.flags & EffectFlags.ACTIVE) {
|
if (e.flags & EffectFlags.ACTIVE) {
|
||||||
try {
|
try {
|
||||||
e.trigger()
|
// ACTIVE flag is effect-only
|
||||||
|
;(e as ReactiveEffect).trigger()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!error) error = err
|
if (!error) error = err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue