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')