fix(reactivity): avoid infinite recursion from side effects in computed getter (#10232)

close #10214
This commit is contained in:
Johnson Chu 2024-02-06 18:44:09 +08:00 committed by GitHub
parent 6c7e0bd88f
commit 0bced13ee5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 58 additions and 16 deletions

View File

@ -482,8 +482,12 @@ describe('reactivity/computed', () => {
c3.value c3.value
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty) expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty) expect(c2.effect._dirtyLevel).toBe(
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty) DirtyLevels.MaybeDirty_ComputedSideEffect,
)
expect(c3.effect._dirtyLevel).toBe(
DirtyLevels.MaybeDirty_ComputedSideEffect,
)
}) })
it('should work when chained(ref+computed)', () => { it('should work when chained(ref+computed)', () => {
@ -550,8 +554,12 @@ describe('reactivity/computed', () => {
c3.value c3.value
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty) expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty) expect(c2.effect._dirtyLevel).toBe(
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty) DirtyLevels.MaybeDirty_ComputedSideEffect,
)
expect(c3.effect._dirtyLevel).toBe(
DirtyLevels.MaybeDirty_ComputedSideEffect,
)
v1.value.v.value = 999 v1.value.v.value = 999
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty) expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
@ -581,4 +589,26 @@ describe('reactivity/computed', () => {
await nextTick() await nextTick()
expect(serializeInner(root)).toBe(`2`) expect(serializeInner(root)).toBe(`2`)
}) })
it('should not trigger effect scheduler by recurse computed effect', async () => {
const v = ref('Hello')
const c = computed(() => {
v.value += ' World'
return v.value
})
const Comp = {
setup: () => {
return () => c.value
},
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
await nextTick()
expect(serializeInner(root)).toBe('Hello World')
v.value += ' World'
await nextTick()
expect(serializeInner(root)).toBe('Hello World World World World')
})
}) })

View File

@ -43,7 +43,13 @@ export class ComputedRefImpl<T> {
) { ) {
this.effect = new ReactiveEffect( this.effect = new ReactiveEffect(
() => getter(this._value), () => getter(this._value),
() => triggerRefValue(this, DirtyLevels.MaybeDirty), () =>
triggerRefValue(
this,
this.effect._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect
? DirtyLevels.MaybeDirty_ComputedSideEffect
: DirtyLevels.MaybeDirty,
),
) )
this.effect.computed = this this.effect.computed = this
this.effect.active = this._cacheable = !isSSR this.effect.active = this._cacheable = !isSSR
@ -60,8 +66,8 @@ export class ComputedRefImpl<T> {
triggerRefValue(self, DirtyLevels.Dirty) triggerRefValue(self, DirtyLevels.Dirty)
} }
trackRefValue(self) trackRefValue(self)
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty) { if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty_ComputedSideEffect) {
triggerRefValue(self, DirtyLevels.MaybeDirty) triggerRefValue(self, DirtyLevels.MaybeDirty_ComputedSideEffect)
} }
return self._value return self._value
} }

View File

@ -25,6 +25,7 @@ export enum ReactiveFlags {
export enum DirtyLevels { export enum DirtyLevels {
NotDirty = 0, NotDirty = 0,
QueryingDirty = 1, QueryingDirty = 1,
MaybeDirty = 2, MaybeDirty_ComputedSideEffect = 2,
Dirty = 3, MaybeDirty = 3,
Dirty = 4,
} }

View File

@ -76,7 +76,10 @@ export class ReactiveEffect<T = any> {
} }
public get dirty() { public get dirty() {
if (this._dirtyLevel === DirtyLevels.MaybeDirty) { if (
this._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect ||
this._dirtyLevel === DirtyLevels.MaybeDirty
) {
this._dirtyLevel = DirtyLevels.QueryingDirty this._dirtyLevel = DirtyLevels.QueryingDirty
pauseTracking() pauseTracking()
for (let i = 0; i < this._depsLength; i++) { for (let i = 0; i < this._depsLength; i++) {
@ -309,7 +312,10 @@ export function triggerEffects(
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo)) effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
} }
effect.trigger() effect.trigger()
if (!effect._runnings || effect.allowRecurse) { if (
(!effect._runnings || effect.allowRecurse) &&
effect._dirtyLevel !== DirtyLevels.MaybeDirty_ComputedSideEffect
) {
effect._shouldSchedule = false effect._shouldSchedule = false
if (effect.scheduler) { if (effect.scheduler) {
queueEffectSchedulers.push(effect.scheduler) queueEffectSchedulers.push(effect.scheduler)

View File

@ -49,11 +49,10 @@ export function trackRefValue(ref: RefBase<any>) {
ref = toRaw(ref) ref = toRaw(ref)
trackEffect( trackEffect(
activeEffect, activeEffect,
ref.dep || (ref.dep ??= createDep(
(ref.dep = createDep( () => (ref.dep = undefined),
() => (ref.dep = undefined), ref instanceof ComputedRefImpl ? ref : undefined,
ref instanceof ComputedRefImpl ? ref : undefined, )),
)),
__DEV__ __DEV__
? { ? {
target: ref, target: ref,