diff --git a/packages/runtime-core/__tests__/apiTemplateRef.spec.ts b/packages/runtime-core/__tests__/apiTemplateRef.spec.ts
index cb5da3a9533..044785b8928 100644
--- a/packages/runtime-core/__tests__/apiTemplateRef.spec.ts
+++ b/packages/runtime-core/__tests__/apiTemplateRef.spec.ts
@@ -268,4 +268,68 @@ describe('api: template refs', () => {
// ref should be updated
expect(serializeInner(root)).toBe(`
foo
`)
})
+
+ // #1789
+ test('the same ref should save the last non-null value', async () => {
+ const root = nodeOps.createElement('div')
+ const toggle = ref(true)
+ const el = ref(null)
+
+ const Comp = {
+ render() {
+ return [
+ h('div', { ref: el }),
+ toggle.value ? h('div', { ref: el }) : null
+ ]
+ }
+ }
+ render(h(Comp), root)
+ expect(el.value).toBe(root.children[2])
+
+ toggle.value = false
+ await nextTick()
+ expect(el.value).toBe(root.children[1])
+
+ toggle.value = true
+ await nextTick()
+ expect(el.value).toBe(root.children[2])
+ })
+
+ // #1789
+ test('the same ref should save the last non-null value about setupState', async () => {
+ const root = nodeOps.createElement('div')
+ const toggle = ref(true)
+ const el = ref(null)
+ let instanceProxy: any
+
+ const Comp = {
+ setup() {
+ return {
+ el
+ }
+ },
+ render() {
+ return [
+ h('div', { ref: 'el' }),
+ toggle.value ? h('div', { ref: 'el' }) : null
+ ]
+ },
+ mounted() {
+ instanceProxy = this
+ }
+ }
+ render(h(Comp), root)
+ expect(el.value).toBe(root.children[2])
+ expect(instanceProxy.$refs.el).toBe(root.children[2])
+
+ toggle.value = false
+ await nextTick()
+ expect(el.value).toBe(root.children[1])
+ expect(instanceProxy.$refs.el).toBe(root.children[1])
+
+ toggle.value = true
+ await nextTick()
+ expect(el.value).toBe(root.children[2])
+ expect(instanceProxy.$refs.el).toBe(root.children[2])
+ })
})
diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts
index 9f045182ce7..a2b4cd69b06 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(() => {
+ // #1789, should save the last non-null value
+ if (value == null) {
+ refs[ref] = value
+ if (hasOwn(setupState, ref)) {
setupState[ref] = value
- }, parentSuspense)
+ }
+ return
}
+ queuePostRenderEffect(() => {
+ refs[ref] = value
+ if (hasOwn(setupState, ref)) {
+ setupState[ref] = value
+ }
+ }, parentSuspense)
} else if (isRef(ref)) {
- ref.value = value
+ if (value == null) {
+ ref.value = value
+ return
+ }
+ queuePostRenderEffect(() => {
+ ref.value = value
+ }, parentSuspense)
} else if (isFunction(ref)) {
callWithErrorHandling(ref, parentComponent, ErrorCodes.FUNCTION_REF, [
value,
diff --git a/packages/vue/__tests__/index.spec.ts b/packages/vue/__tests__/index.spec.ts
index 974fffc0442..294eb33d3e5 100644
--- a/packages/vue/__tests__/index.spec.ts
+++ b/packages/vue/__tests__/index.spec.ts
@@ -71,6 +71,41 @@ describe('compiler + runtime integration', () => {
expect(one.destroyed).toHaveBeenCalledTimes(0)
})
+ // #1789
+ it('$refs should refer to the correct value', async () => {
+ const container = document.createElement('div')
+ const toggle = ref(true)
+ let instanceProxy: any
+ const App = {
+ template: `
+ true
+ false
+ `,
+ data() {
+ return {
+ toggle
+ }
+ },
+ mounted() {
+ instanceProxy = this
+ }
+ }
+
+ createApp(App).mount(container)
+ expect(container.innerHTML).toBe('true
')
+ expect(instanceProxy.$refs.divEl).toBe(container.children[0])
+
+ toggle.value = false
+ await nextTick()
+ expect(container.innerHTML).toBe('false
')
+ expect(instanceProxy.$refs.divEl).toBe(container.children[0])
+
+ toggle.value = true
+ await nextTick()
+ expect(container.innerHTML).toBe('true
')
+ expect(instanceProxy.$refs.divEl).toBe(container.children[0])
+ })
+
it('should support runtime template via CSS ID selector', () => {
const container = document.createElement('div')
const template = document.createElement('div')