From 0b23fd23833cf085e7e112bf4435cfc9b360d072 Mon Sep 17 00:00:00 2001 From: edison Date: Thu, 1 May 2025 22:02:17 +0800 Subject: [PATCH] fix(reactivity): should not recompute if computed does not track reactive data (#12341) close #12337 --- packages/reactivity/__tests__/computed.spec.ts | 11 +++++++++++ packages/reactivity/src/effect.ts | 14 ++++++++------ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/packages/reactivity/__tests__/computed.spec.ts b/packages/reactivity/__tests__/computed.spec.ts index 123df44f2..c7963499f 100644 --- a/packages/reactivity/__tests__/computed.spec.ts +++ b/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) diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts index 886f380dd..84cf184c1 100644 --- a/packages/reactivity/src/effect.ts +++ b/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++ }