diff --git a/packages/reactivity/src/effectScope.ts b/packages/reactivity/src/effectScope.ts index cb4e057c4..92ad92c12 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 7d2a1e73c..39032a636 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) + }) })