diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index c96db41fce4..1836b6e9392 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -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 + + const Child = defineComponent({ + render: () => h('div'), + mounted() { + instance = getCurrentInstance() + }, + unmounted() {} + }) + + const Comp = defineComponent({ + setup() { + const comp = ref() + 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) + }) }) diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 62592d3938c..af54568823f 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -285,7 +285,7 @@ function doWatch( scheduler }) - recordInstanceBoundEffect(runner) + recordInstanceBoundEffect(runner, instance) // initial run if (cb) { diff --git a/packages/runtime-core/src/component.ts b/packages/runtime-core/src/component.ts index 935c4d3758f..99b261a68ae 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -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) } }