From 22dcbf3e20eb84f69c8952f6f70d9990136a4a68 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 8 Jan 2025 18:07:44 +0800 Subject: [PATCH] fix(reactivity): ensure multiple effectScope on() and off() calls maintains correct active scope close #12631 close #12632 This is a combination of changes from both 8dec243 and #12641 --- packages/reactivity/src/effectScope.ts | 15 ++++++++-- .../runtime-core/__tests__/apiWatch.spec.ts | 28 +++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/packages/reactivity/src/effectScope.ts b/packages/reactivity/src/effectScope.ts index cb4e057c480..92ad92c1249 100644 --- a/packages/reactivity/src/effectScope.ts +++ b/packages/reactivity/src/effectScope.ts @@ -8,6 +8,10 @@ export class EffectScope { * @internal */ private _active = true + /** + * @internal track `on` calls, allow `on` call multiple times + */ + private _on = 0 /** * @internal */ @@ -99,12 +103,16 @@ export class EffectScope { } } + prevScope: EffectScope | undefined /** * This should only be called on non-detached scopes * @internal */ on(): void { - activeEffectScope = this + if (++this._on === 1) { + this.prevScope = activeEffectScope + activeEffectScope = this + } } /** @@ -112,7 +120,10 @@ export class EffectScope { * @internal */ off(): void { - activeEffectScope = this.parent + if (this._on > 0 && --this._on === 0) { + activeEffectScope = this.prevScope + this.prevScope = undefined + } } stop(fromParent?: boolean): void { diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 7d2a1e73c08..39032a63699 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -31,6 +31,7 @@ import { TrackOpTypes, TriggerOpTypes, effectScope, + onScopeDispose, shallowReactive, shallowRef, toRef, @@ -1982,4 +1983,31 @@ describe('api: watch', () => { expect(spy1).toHaveBeenCalled() expect(spy2).toHaveBeenCalled() }) + + // #12631 + test('this.$watch w/ onScopeDispose', () => { + const onCleanup = vi.fn() + const toggle = ref(true) + + const Comp = defineComponent({ + render() {}, + created(this: any) { + this.$watch( + () => 1, + function () {}, + ) + onScopeDispose(onCleanup) + }, + }) + + const App = defineComponent({ + render() { + return toggle.value ? h(Comp) : null + }, + }) + + const root = nodeOps.createElement('div') + createApp(App).mount(root) + expect(onCleanup).toBeCalledTimes(0) + }) })