Skip to content

Commit 0bced13

Browse files
authoredFeb 6, 2024··
fix(reactivity): avoid infinite recursion from side effects in computed getter (#10232)
close #10214
1 parent 6c7e0bd commit 0bced13

File tree

5 files changed

+58
-16
lines changed

5 files changed

+58
-16
lines changed
 

‎packages/reactivity/__tests__/computed.spec.ts

+34-4
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,12 @@ describe('reactivity/computed', () => {
482482
c3.value
483483

484484
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
485-
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
486-
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
485+
expect(c2.effect._dirtyLevel).toBe(
486+
DirtyLevels.MaybeDirty_ComputedSideEffect,
487+
)
488+
expect(c3.effect._dirtyLevel).toBe(
489+
DirtyLevels.MaybeDirty_ComputedSideEffect,
490+
)
487491
})
488492

489493
it('should work when chained(ref+computed)', () => {
@@ -550,8 +554,12 @@ describe('reactivity/computed', () => {
550554

551555
c3.value
552556
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
553-
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
554-
expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
557+
expect(c2.effect._dirtyLevel).toBe(
558+
DirtyLevels.MaybeDirty_ComputedSideEffect,
559+
)
560+
expect(c3.effect._dirtyLevel).toBe(
561+
DirtyLevels.MaybeDirty_ComputedSideEffect,
562+
)
555563

556564
v1.value.v.value = 999
557565
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
@@ -581,4 +589,26 @@ describe('reactivity/computed', () => {
581589
await nextTick()
582590
expect(serializeInner(root)).toBe(`2`)
583591
})
592+
593+
it('should not trigger effect scheduler by recurse computed effect', async () => {
594+
const v = ref('Hello')
595+
const c = computed(() => {
596+
v.value += ' World'
597+
return v.value
598+
})
599+
const Comp = {
600+
setup: () => {
601+
return () => c.value
602+
},
603+
}
604+
const root = nodeOps.createElement('div')
605+
606+
render(h(Comp), root)
607+
await nextTick()
608+
expect(serializeInner(root)).toBe('Hello World')
609+
610+
v.value += ' World'
611+
await nextTick()
612+
expect(serializeInner(root)).toBe('Hello World World World World')
613+
})
584614
})

‎packages/reactivity/src/computed.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,13 @@ export class ComputedRefImpl<T> {
4343
) {
4444
this.effect = new ReactiveEffect(
4545
() => getter(this._value),
46-
() => triggerRefValue(this, DirtyLevels.MaybeDirty),
46+
() =>
47+
triggerRefValue(
48+
this,
49+
this.effect._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect
50+
? DirtyLevels.MaybeDirty_ComputedSideEffect
51+
: DirtyLevels.MaybeDirty,
52+
),
4753
)
4854
this.effect.computed = this
4955
this.effect.active = this._cacheable = !isSSR
@@ -60,8 +66,8 @@ export class ComputedRefImpl<T> {
6066
triggerRefValue(self, DirtyLevels.Dirty)
6167
}
6268
trackRefValue(self)
63-
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty) {
64-
triggerRefValue(self, DirtyLevels.MaybeDirty)
69+
if (self.effect._dirtyLevel >= DirtyLevels.MaybeDirty_ComputedSideEffect) {
70+
triggerRefValue(self, DirtyLevels.MaybeDirty_ComputedSideEffect)
6571
}
6672
return self._value
6773
}

‎packages/reactivity/src/constants.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export enum ReactiveFlags {
2525
export enum DirtyLevels {
2626
NotDirty = 0,
2727
QueryingDirty = 1,
28-
MaybeDirty = 2,
29-
Dirty = 3,
28+
MaybeDirty_ComputedSideEffect = 2,
29+
MaybeDirty = 3,
30+
Dirty = 4,
3031
}

‎packages/reactivity/src/effect.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ export class ReactiveEffect<T = any> {
7676
}
7777

7878
public get dirty() {
79-
if (this._dirtyLevel === DirtyLevels.MaybeDirty) {
79+
if (
80+
this._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect ||
81+
this._dirtyLevel === DirtyLevels.MaybeDirty
82+
) {
8083
this._dirtyLevel = DirtyLevels.QueryingDirty
8184
pauseTracking()
8285
for (let i = 0; i < this._depsLength; i++) {
@@ -309,7 +312,10 @@ export function triggerEffects(
309312
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
310313
}
311314
effect.trigger()
312-
if (!effect._runnings || effect.allowRecurse) {
315+
if (
316+
(!effect._runnings || effect.allowRecurse) &&
317+
effect._dirtyLevel !== DirtyLevels.MaybeDirty_ComputedSideEffect
318+
) {
313319
effect._shouldSchedule = false
314320
if (effect.scheduler) {
315321
queueEffectSchedulers.push(effect.scheduler)

‎packages/reactivity/src/ref.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,10 @@ export function trackRefValue(ref: RefBase<any>) {
4949
ref = toRaw(ref)
5050
trackEffect(
5151
activeEffect,
52-
ref.dep ||
53-
(ref.dep = createDep(
54-
() => (ref.dep = undefined),
55-
ref instanceof ComputedRefImpl ? ref : undefined,
56-
)),
52+
(ref.dep ??= createDep(
53+
() => (ref.dep = undefined),
54+
ref instanceof ComputedRefImpl ? ref : undefined,
55+
)),
5756
__DEV__
5857
? {
5958
target: ref,

0 commit comments

Comments
 (0)
Please sign in to comment.