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,