Skip to content

Commit 735af1c

Browse files
authored
fix(runtime-core): ensure watchers are always registered to correct instance owner (#2495)
close: #2381
1 parent ce4915d commit 735af1c

File tree

3 files changed

+68
-7
lines changed

3 files changed

+68
-7
lines changed

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

+61-3
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,26 @@ import {
55
computed,
66
nextTick,
77
ref,
8-
h
8+
defineComponent,
9+
getCurrentInstance,
10+
ComponentInternalInstance,
11+
ComponentPublicInstance
912
} from '../src/index'
10-
import { render, nodeOps, serializeInner, TestElement } from '@vue/runtime-test'
13+
import {
14+
render,
15+
nodeOps,
16+
serializeInner,
17+
TestElement,
18+
h
19+
} from '@vue/runtime-test'
1120
import {
1221
ITERATE_KEY,
1322
DebuggerEvent,
1423
TrackOpTypes,
1524
TriggerOpTypes,
1625
triggerRef,
17-
shallowRef
26+
shallowRef,
27+
Ref
1828
} from '@vue/reactivity'
1929

2030
// reference: https://vue-composition-api-rfc.netlify.com/api.html#watch
@@ -799,4 +809,52 @@ describe('api: watch', () => {
799809
await nextTick()
800810
expect(spy).toHaveBeenCalledTimes(1)
801811
})
812+
813+
// https://github.com/vuejs/vue-next/issues/2381
814+
test('$watch should always register its effects with itw own instance', async () => {
815+
let instance: ComponentInternalInstance | null
816+
let _show: Ref<boolean>
817+
818+
const Child = defineComponent({
819+
render: () => h('div'),
820+
mounted() {
821+
instance = getCurrentInstance()
822+
},
823+
unmounted() {}
824+
})
825+
826+
const Comp = defineComponent({
827+
setup() {
828+
const comp = ref<ComponentPublicInstance | undefined>()
829+
const show = ref(true)
830+
_show = show
831+
return { comp, show }
832+
},
833+
render() {
834+
return this.show
835+
? h(Child, {
836+
ref: vm => void (this.comp = vm as ComponentPublicInstance)
837+
})
838+
: null
839+
},
840+
mounted() {
841+
// this call runs while Comp is currentInstance, but
842+
// the effect for this `$watch` should nontheless be registered with Child
843+
this.comp!.$watch(() => this.show, () => void 0)
844+
}
845+
})
846+
847+
render(h(Comp), nodeOps.createElement('div'))
848+
849+
expect(instance!).toBeDefined()
850+
expect(instance!.effects).toBeInstanceOf(Array)
851+
expect(instance!.effects!.length).toBe(1)
852+
853+
_show!.value = false
854+
855+
await nextTick()
856+
await nextTick()
857+
858+
expect(instance!.effects![0].active).toBe(false)
859+
})
802860
})

packages/runtime-core/src/apiWatch.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ function doWatch(
285285
scheduler
286286
})
287287

288-
recordInstanceBoundEffect(runner)
288+
recordInstanceBoundEffect(runner, instance)
289289

290290
// initial run
291291
if (cb) {

packages/runtime-core/src/component.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -786,9 +786,12 @@ export function createSetupContext(
786786

787787
// record effects created during a component's setup() so that they can be
788788
// stopped when the component unmounts
789-
export function recordInstanceBoundEffect(effect: ReactiveEffect) {
790-
if (currentInstance) {
791-
;(currentInstance.effects || (currentInstance.effects = [])).push(effect)
789+
export function recordInstanceBoundEffect(
790+
effect: ReactiveEffect,
791+
instance = currentInstance
792+
) {
793+
if (instance) {
794+
;(instance.effects || (instance.effects = [])).push(effect)
792795
}
793796
}
794797

0 commit comments

Comments
 (0)