From b75133944cd86a750cb43d893df7b5e98f6a58f3 Mon Sep 17 00:00:00 2001 From: Thorsten Luenborg Date: Tue, 27 Oct 2020 12:01:15 +0100 Subject: [PATCH] fix(runtime-core): $watch effects: it should always add its effects to its own instance close: #2381 --- .../runtime-core/__tests__/apiWatch.spec.ts | 64 ++++++++++++++++++- packages/runtime-core/src/apiWatch.ts | 2 +- packages/runtime-core/src/component.ts | 9 ++- 3 files changed, 68 insertions(+), 7 deletions(-) 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 8f089eb4ee3..e0411b18561 100644 --- a/packages/runtime-core/src/component.ts +++ b/packages/runtime-core/src/component.ts @@ -756,9 +756,12 @@ function createSetupContext(instance: ComponentInternalInstance): SetupContext { // 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) } }