From d2d89551bb06dc05cb7ae0496b8f345ae0de78ed Mon Sep 17 00:00:00 2001 From: Yang Mingshan Date: Thu, 4 Jan 2024 10:36:13 +0800 Subject: [PATCH] fix(watch): cleanup watcher effect from scope when manually stopped (#9978) --- .../runtime-core/__tests__/apiWatch.spec.ts | 31 +++++++++++++++++++ packages/runtime-core/src/apiWatch.ts | 6 ++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 3656b0a64ba..fe299edbb63 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1443,4 +1443,35 @@ describe('api: watch', () => { expect(spy1).toHaveBeenCalledTimes(1) expect(spy2).toHaveBeenCalledTimes(1) }) + + test("effect should be removed from scope's effects after it is stopped", () => { + const num = ref(0) + let unwatch: () => void + + let instance: ComponentInternalInstance + const Comp = { + setup() { + instance = getCurrentInstance()! + unwatch = watch(num, () => { + console.log(num.value) + }) + return () => null + }, + } + const root = nodeOps.createElement('div') + createApp(Comp).mount(root) + expect(instance!.scope.effects.length).toBe(2) + unwatch!() + expect(instance!.scope.effects.length).toBe(1) + + const scope = effectScope() + scope.run(() => { + unwatch = watch(num, () => { + console.log(num.value) + }) + }) + expect(scope.effects.length).toBe(1) + unwatch!() + expect(scope.effects.length).toBe(0) + }) }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 7fbcc78f36f..3a2d9e46c33 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -5,6 +5,7 @@ import { ReactiveEffect, ReactiveFlags, type Ref, + getCurrentScope, isReactive, isRef, isShallow, @@ -394,10 +395,11 @@ function doWatch( const effect = new ReactiveEffect(getter, NOOP, scheduler) + const scope = getCurrentScope() const unwatch = () => { effect.stop() - if (instance && instance.scope) { - remove(instance.scope.effects!, effect) + if (scope) { + remove(scope.effects, effect) } }