From a9d47854bb948b6c45b1e31577cce31ecc9aad16 Mon Sep 17 00:00:00 2001 From: Thorsten Luenborg Date: Mon, 12 Dec 2022 20:51:10 +0100 Subject: [PATCH 1/2] fix(reactivity): ensure watch(Effect) can run independent of unmounted instance if created in a detatched effectScope --- .../runtime-core/__tests__/apiWatch.spec.ts | 33 +++++++++++++++++++ packages/runtime-core/src/apiWatch.ts | 6 ++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index f6bad14ddb9..36a2d10fb8d 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1150,4 +1150,37 @@ describe('api: watch', () => { // own update effect expect(instance!.scope.effects.length).toBe(1) }) + + test('watchEffect should keep running if created in a detatched scope', async () => { + const trigger = ref(0) + let count = 0 + const Comp = { + setup() { + effectScope(true).run(() => { + watchEffect( + () => { + trigger.value + count++ + }, + ) + watch( + trigger, + () => count++ + ) + }) + return () => '' + } + } + const root = nodeOps.createElement('div') + render(h(Comp), root) + expect(count).toBe(1) // only watchEffect as ran so far + trigger.value++ + await nextTick() + expect(count).toBe(3) // both watchers run while component is mounted + render(null, root) + await nextTick() + trigger.value++ + await nextTick() + expect(count).toBe(5) // both watchers run again event though component has been unmounted + }) }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index ce13f0bb805..a85e0e93be6 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -7,7 +7,8 @@ import { isReactive, ReactiveFlags, EffectScheduler, - DebuggerOptions + DebuggerOptions, + getCurrentScope, } from '@vue/reactivity' import { SchedulerJob, queueJob } from './scheduler' import { @@ -197,7 +198,8 @@ function doWatch( ) } - const instance = currentInstance + const instance = getCurrentScope() === currentInstance?.scope ? currentInstance : null + // const instance = currentInstance let getter: () => any let forceTrigger = false let isMultiSource = false From 314673be0b76bc825f8d096e8e127c5d353abd8e Mon Sep 17 00:00:00 2001 From: Thorsten Luenborg Date: Tue, 13 Dec 2022 10:09:55 +0100 Subject: [PATCH 2/2] test: use separate counters for each watcher to make test more robust --- .../runtime-core/__tests__/apiWatch.spec.ts | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 36a2d10fb8d..6de8f95dec6 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -1153,19 +1153,20 @@ describe('api: watch', () => { test('watchEffect should keep running if created in a detatched scope', async () => { const trigger = ref(0) - let count = 0 + let countWE = 0 + let countW = 0 const Comp = { setup() { effectScope(true).run(() => { watchEffect( () => { trigger.value - count++ + countWE++ }, ) watch( trigger, - () => count++ + () => countW++ ) }) return () => '' @@ -1173,14 +1174,20 @@ describe('api: watch', () => { } const root = nodeOps.createElement('div') render(h(Comp), root) - expect(count).toBe(1) // only watchEffect as ran so far + // only watchEffect as ran so far + expect(countWE).toBe(1) + expect(countW).toBe(0) trigger.value++ await nextTick() - expect(count).toBe(3) // both watchers run while component is mounted - render(null, root) + // both watchers run while component is mounted + expect(countWE).toBe(2) + expect(countW).toBe(1) + render(null, root) // unmount await nextTick() trigger.value++ await nextTick() - expect(count).toBe(5) // both watchers run again event though component has been unmounted + // both watchers run again event though component has been unmounted + expect(countWE).toBe(3) + expect(countW).toBe(2) }) })