diff --git a/packages/runtime-core/__tests__/apiTemplateRef.spec.ts b/packages/runtime-core/__tests__/apiTemplateRef.spec.ts index cb5da3a9533..808731c230e 100644 --- a/packages/runtime-core/__tests__/apiTemplateRef.spec.ts +++ b/packages/runtime-core/__tests__/apiTemplateRef.spec.ts @@ -268,4 +268,61 @@ describe('api: template refs', () => { // ref should be updated expect(serializeInner(root)).toBe(`
foo
`) }) + + // #1834 + test('exchange refs', async () => { + const refToggle = ref(false) + const spy = jest.fn() + + const Comp = { + render(this: any) { + return [ + h('p', { ref: refToggle.value ? 'foo' : 'bar' }), + h('i', { ref: refToggle.value ? 'bar' : 'foo' }) + ] + }, + mounted(this: any) { + spy(this.$refs.foo.tag, this.$refs.bar.tag) + }, + updated(this: any) { + spy(this.$refs.foo.tag, this.$refs.bar.tag) + } + } + + const root = nodeOps.createElement('div') + render(h(Comp), root) + + expect(spy.mock.calls[0][0]).toBe('i') + expect(spy.mock.calls[0][1]).toBe('p') + refToggle.value = true + await nextTick() + expect(spy.mock.calls[1][0]).toBe('p') + expect(spy.mock.calls[1][1]).toBe('i') + }) + + // #1789 + test('toggle the same ref to different elements', async () => { + const refToggle = ref(false) + const spy = jest.fn() + + const Comp = { + render(this: any) { + return refToggle.value ? h('p', { ref: 'foo' }) : h('i', { ref: 'foo' }) + }, + mounted(this: any) { + spy(this.$refs.foo.tag) + }, + updated(this: any) { + spy(this.$refs.foo.tag) + } + } + + const root = nodeOps.createElement('div') + render(h(Comp), root) + + expect(spy.mock.calls[0][0]).toBe('i') + refToggle.value = true + await nextTick() + expect(spy.mock.calls[1][0]).toBe('p') + }) }) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 9f045182ce7..6b3b2cfab1c 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -319,14 +319,28 @@ export const setRef = ( } if (isString(ref)) { - refs[ref] = value - if (hasOwn(setupState, ref)) { - queuePostRenderEffect(() => { + const doSet = () => { + refs[ref] = value + if (hasOwn(setupState, ref)) { setupState[ref] = value - }, parentSuspense) + } + } + // #1789: for non-null values, set them after render + // null values means this is unmount and it should not overwrite another + // ref with the same key + if (value) { + queuePostRenderEffect(doSet, parentSuspense) + } else { + doSet() } } else if (isRef(ref)) { - ref.value = value + if (value) { + queuePostRenderEffect(() => { + ref.value = value + }, parentSuspense) + } else { + ref.value = value + } } else if (isFunction(ref)) { callWithErrorHandling(ref, parentComponent, ErrorCodes.FUNCTION_REF, [ value,