]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(reactivity): ensure multiple effectScope on() and off() calls maintains correct...
authorEvan You <evan@vuejs.org>
Wed, 8 Jan 2025 10:07:44 +0000 (18:07 +0800)
committerEvan You <evan@vuejs.org>
Wed, 8 Jan 2025 10:07:44 +0000 (18:07 +0800)
close #12631
close #12632

This is a combination of changes from both 8dec243 and #12641

packages/reactivity/src/effectScope.ts
packages/runtime-core/__tests__/apiWatch.spec.ts

index cb4e057c480fcbee3b695142a1cdc46854c0af56..92ad92c12490fe5ce1014144fbe4175b207481e6 100644 (file)
@@ -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 {
index 7d2a1e73c0830e3c3bc63aedc0bc44654d92b081..39032a63699ff0259fd401d079a7c5b4e15b2829 100644 (file)
@@ -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)
+  })
 })