Просмотр исходного кода

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

close #10214
Johnson Chu 2 лет назад
Родитель
Сommit
0bced13ee5

+ 34 - 4
packages/reactivity/__tests__/computed.spec.ts

@@ -482,8 +482,12 @@ describe('reactivity/computed', () => {
     c3.value
 
     expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
-    expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
-    expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
+    expect(c2.effect._dirtyLevel).toBe(
+      DirtyLevels.MaybeDirty_ComputedSideEffect,
+    )
+    expect(c3.effect._dirtyLevel).toBe(
+      DirtyLevels.MaybeDirty_ComputedSideEffect,
+    )
   })
 
   it('should work when chained(ref+computed)', () => {
@@ -550,8 +554,12 @@ describe('reactivity/computed', () => {
 
     c3.value
     expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
-    expect(c2.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
-    expect(c3.effect._dirtyLevel).toBe(DirtyLevels.MaybeDirty)
+    expect(c2.effect._dirtyLevel).toBe(
+      DirtyLevels.MaybeDirty_ComputedSideEffect,
+    )
+    expect(c3.effect._dirtyLevel).toBe(
+      DirtyLevels.MaybeDirty_ComputedSideEffect,
+    )
 
     v1.value.v.value = 999
     expect(c1.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
@@ -581,4 +589,26 @@ describe('reactivity/computed', () => {
     await nextTick()
     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')
+  })
 })

+ 9 - 3
packages/reactivity/src/computed.ts

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

+ 3 - 2
packages/reactivity/src/constants.ts

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

+ 8 - 2
packages/reactivity/src/effect.ts

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

+ 4 - 5
packages/reactivity/src/ref.ts

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