diff --git a/packages/runtime-core/__tests__/helpers/useTemplateRef.spec.ts b/packages/runtime-core/__tests__/helpers/useTemplateRef.spec.ts index adc8ed66c77..82b395e3b9d 100644 --- a/packages/runtime-core/__tests__/helpers/useTemplateRef.spec.ts +++ b/packages/runtime-core/__tests__/helpers/useTemplateRef.spec.ts @@ -1,10 +1,13 @@ import { type ShallowRef, + getCurrentInstance, h, + isReactive, nextTick, nodeOps, ref, render, + serializeInner, useTemplateRef, } from '@vue/runtime-test' @@ -70,6 +73,58 @@ describe('useTemplateRef', () => { expect(t1!.value).toBe(null) }) + // #12731 + test('should collect refs as reactive array in v-for', async () => { + let t1: any + const list = ref([]) + let currentInstance: any + const Comp = { + setup() { + t1 = useTemplateRef('refKey') + currentInstance = getCurrentInstance()! + }, + render() { + return h('div', null, [ + h('div', null, String(t1.value?.length)), + h( + 'ul', + list.value.map(i => + h( + 'li', + { + ref: 'refKey', + ref_for: true, + }, + i, + ), + ), + ), + ]) + }, + } + const root = nodeOps.createElement('div') + render(h(Comp), root) + expect(t1!.value).toBe(null) + expect(serializeInner(root)).toBe( + '
undefined
', + ) + + list.value.push(1) + await nextTick() + expect(isReactive(currentInstance.refs['refKey'])).toBe(true) + expect(t1!.value.length).toBe(1) + expect(serializeInner(root)).toBe( + '
1
', + ) + + list.value.push(2) + await nextTick() + expect(t1!.value.length).toBe(2) + expect(serializeInner(root)).toBe( + '
2
', + ) + }) + test('should warn on duplicate useTemplateRef', () => { const root = nodeOps.createElement('div') render( diff --git a/packages/runtime-core/src/rendererTemplateRef.ts b/packages/runtime-core/src/rendererTemplateRef.ts index ca21030dc35..76f50c4a81a 100644 --- a/packages/runtime-core/src/rendererTemplateRef.ts +++ b/packages/runtime-core/src/rendererTemplateRef.ts @@ -11,7 +11,7 @@ import { } from '@vue/shared' import { isAsyncWrapper } from './apiAsyncComponent' import { warn } from './warning' -import { isRef, toRaw } from '@vue/reactivity' +import { isRef, shallowReactive, toRaw } from '@vue/reactivity' import { ErrorCodes, callWithErrorHandling } from './errorHandling' import type { SchedulerJob } from './scheduler' import { queuePostRenderEffect } from './renderer' @@ -125,7 +125,7 @@ export function setRef( } else { if (!isArray(existing)) { if (_isString) { - refs[ref] = [refValue] + refs[ref] = shallowReactive([refValue]) if (canSetSetupRef(ref)) { setupState[ref] = refs[ref] }