Browse Source

fix(reactivity): should not recompute if computed does not track reactive data (#12341)

close #12337
edison 11 months ago
parent
commit
0b23fd2383
2 changed files with 19 additions and 6 deletions
  1. 11 0
      packages/reactivity/__tests__/computed.spec.ts
  2. 8 6
      packages/reactivity/src/effect.ts

+ 11 - 0
packages/reactivity/__tests__/computed.spec.ts

@@ -1012,6 +1012,17 @@ describe('reactivity/computed', () => {
     expect(cValue.value).toBe(1)
   })
 
+  test('should not recompute if computed does not track reactive data', async () => {
+    const spy = vi.fn()
+    const c1 = computed(() => spy())
+
+    c1.value
+    ref(0).value++ // update globalVersion
+    c1.value
+
+    expect(spy).toBeCalledTimes(1)
+  })
+
   test('computed should remain live after losing all subscribers', () => {
     const state = reactive({ a: 1 })
     const p = computed(() => state.a + 1)

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

@@ -49,6 +49,7 @@ export enum EffectFlags {
   DIRTY = 1 << 4,
   ALLOW_RECURSE = 1 << 5,
   PAUSED = 1 << 6,
+  EVALUATED = 1 << 7,
 }
 
 /**
@@ -377,22 +378,22 @@ export function refreshComputed(computed: ComputedRefImpl): undefined {
   }
   computed.globalVersion = globalVersion
 
-  const dep = computed.dep
-  computed.flags |= EffectFlags.RUNNING
   // In SSR there will be no render effect, so the computed has no subscriber
   // and therefore tracks no deps, thus we cannot rely on the dirty check.
   // Instead, computed always re-evaluate and relies on the globalVersion
   // fast path above for caching.
+  // #12337 if computed has no deps (does not rely on any reactive data) and evaluated,
+  // there is no need to re-evaluate.
   if (
-    dep.version > 0 &&
     !computed.isSSR &&
-    computed.deps &&
-    !isDirty(computed)
+    computed.flags & EffectFlags.EVALUATED &&
+    ((!computed.deps && !(computed as any)._dirty) || !isDirty(computed))
   ) {
-    computed.flags &= ~EffectFlags.RUNNING
     return
   }
+  computed.flags |= EffectFlags.RUNNING
 
+  const dep = computed.dep
   const prevSub = activeSub
   const prevShouldTrack = shouldTrack
   activeSub = computed
@@ -402,6 +403,7 @@ export function refreshComputed(computed: ComputedRefImpl): undefined {
     prepareDeps(computed)
     const value = computed.fn(computed._value)
     if (dep.version === 0 || hasChanged(value, computed._value)) {
+      computed.flags |= EffectFlags.EVALUATED
       computed._value = value
       dep.version++
     }