]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(runtime-core): ensure watchers are always registered to correct instance owner...
authorThorsten Lünborg <t.luenborg@googlemail.com>
Fri, 27 Nov 2020 14:31:50 +0000 (15:31 +0100)
committerGitHub <noreply@github.com>
Fri, 27 Nov 2020 14:31:50 +0000 (09:31 -0500)
close: #2381

packages/runtime-core/__tests__/apiWatch.spec.ts
packages/runtime-core/src/apiWatch.ts
packages/runtime-core/src/component.ts

index c96db41fce4cc1e8c146ba94ba334c2ff09d720a..1836b6e93922beb0dc9af963dd0e679e2b298350 100644 (file)
@@ -5,16 +5,26 @@ import {
   computed,
   nextTick,
   ref,
-  h
+  defineComponent,
+  getCurrentInstance,
+  ComponentInternalInstance,
+  ComponentPublicInstance
 } from '../src/index'
-import { render, nodeOps, serializeInner, TestElement } from '@vue/runtime-test'
+import {
+  render,
+  nodeOps,
+  serializeInner,
+  TestElement,
+  h
+} from '@vue/runtime-test'
 import {
   ITERATE_KEY,
   DebuggerEvent,
   TrackOpTypes,
   TriggerOpTypes,
   triggerRef,
-  shallowRef
+  shallowRef,
+  Ref
 } from '@vue/reactivity'
 
 // reference: https://vue-composition-api-rfc.netlify.com/api.html#watch
@@ -799,4 +809,52 @@ describe('api: watch', () => {
     await nextTick()
     expect(spy).toHaveBeenCalledTimes(1)
   })
+
+  // https://github.com/vuejs/vue-next/issues/2381
+  test('$watch should always register its effects with itw own instance', async () => {
+    let instance: ComponentInternalInstance | null
+    let _show: Ref<boolean>
+
+    const Child = defineComponent({
+      render: () => h('div'),
+      mounted() {
+        instance = getCurrentInstance()
+      },
+      unmounted() {}
+    })
+
+    const Comp = defineComponent({
+      setup() {
+        const comp = ref<ComponentPublicInstance | undefined>()
+        const show = ref(true)
+        _show = show
+        return { comp, show }
+      },
+      render() {
+        return this.show
+          ? h(Child, {
+              ref: vm => void (this.comp = vm as ComponentPublicInstance)
+            })
+          : null
+      },
+      mounted() {
+        // this call runs while Comp is currentInstance, but
+        // the effect for this `$watch` should nontheless be registered with Child
+        this.comp!.$watch(() => this.show, () => void 0)
+      }
+    })
+
+    render(h(Comp), nodeOps.createElement('div'))
+
+    expect(instance!).toBeDefined()
+    expect(instance!.effects).toBeInstanceOf(Array)
+    expect(instance!.effects!.length).toBe(1)
+
+    _show!.value = false
+
+    await nextTick()
+    await nextTick()
+
+    expect(instance!.effects![0].active).toBe(false)
+  })
 })
index 62592d3938cf93c9fa3c0e813d17f99b67e7e62d..af54568823f17fe87c50b862a23160e41f43ddca 100644 (file)
@@ -285,7 +285,7 @@ function doWatch(
     scheduler
   })
 
-  recordInstanceBoundEffect(runner)
+  recordInstanceBoundEffect(runner, instance)
 
   // initial run
   if (cb) {
index 935c4d3758fb4dd1dcf75d0c9d8ebf41395ca1c2..99b261a68aecf9d85ccc0bd5c5f201adf7d91d2a 100644 (file)
@@ -786,9 +786,12 @@ export function createSetupContext(
 
 // record effects created during a component's setup() so that they can be
 // stopped when the component unmounts
-export function recordInstanceBoundEffect(effect: ReactiveEffect) {
-  if (currentInstance) {
-    ;(currentInstance.effects || (currentInstance.effects = [])).push(effect)
+export function recordInstanceBoundEffect(
+  effect: ReactiveEffect,
+  instance = currentInstance
+) {
+  if (instance) {
+    ;(instance.effects || (instance.effects = [])).push(effect)
   }
 }