Просмотр исходного кода

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
Evan You 1 год назад
Родитель
Сommit
22dcbf3e20

+ 13 - 2
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 {

+ 28 - 0
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)
+  })
 })