From f93419911649816e83a7357271184ca4e909729f Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 17 Jan 2025 09:17:28 +0800 Subject: [PATCH 1/2] fix(useTemplateRef): handle useTemplateRef edge case with vFor --- packages/runtime-core/src/rendererTemplateRef.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/runtime-core/src/rendererTemplateRef.ts b/packages/runtime-core/src/rendererTemplateRef.ts index ca21030dc35..ae746fe0c83 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,12 +125,12 @@ export function setRef( } else { if (!isArray(existing)) { if (_isString) { - refs[ref] = [refValue] + refs[ref] = shallowReactive([refValue]) if (canSetSetupRef(ref)) { setupState[ref] = refs[ref] } } else { - ref.value = [refValue] + ref.value = shallowReactive([refValue]) if (rawRef.k) refs[rawRef.k] = ref.value } } else if (!existing.includes(refValue)) { From 34a473bdaf715443614f86352ee1dbbbae2ffbca Mon Sep 17 00:00:00 2001 From: daiwei Date: Fri, 17 Jan 2025 11:17:59 +0800 Subject: [PATCH 2/2] test: add test --- .../__tests__/helpers/useTemplateRef.spec.ts | 55 +++++++++++++++++++ .../runtime-core/src/rendererTemplateRef.ts | 2 +- 2 files changed, 56 insertions(+), 1 deletion(-) 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
    • 1
    ', + ) + + list.value.push(2) + await nextTick() + expect(t1!.value.length).toBe(2) + expect(serializeInner(root)).toBe( + '
    2
    • 1
    • 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 ae746fe0c83..76f50c4a81a 100644 --- a/packages/runtime-core/src/rendererTemplateRef.ts +++ b/packages/runtime-core/src/rendererTemplateRef.ts @@ -130,7 +130,7 @@ export function setRef( setupState[ref] = refs[ref] } } else { - ref.value = shallowReactive([refValue]) + ref.value = [refValue] if (rawRef.k) refs[rawRef.k] = ref.value } } else if (!existing.includes(refValue)) {