You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue中useTemplateRef生成的数组为何不具备响应式?

响应式监听v-for生成的模板引用数组变化的解决方案

我需要对通过v-for遍历对象集合生成的模板引用数组的变化做出响应,并将该数组传递给组件/组合式函数以实现响应式。Vue官方文档提供了将这类元素保存到数组的方法,但测试后发现useTemplateRef返回的是浅引用(ShallowRef),无法监听数组内容的变化。

测试代码

<script setup>
import { ref, useTemplateRef, watch, markRaw } from 'vue'

const list = ref([1, 2, 3])

const itemRefs = useTemplateRef('items')

watch(itemRefs, () => {
  console.log("itemsRef:", itemRefs.value.length);
}, {deep: true});

watch(list, () => {
  console.log("list:", list.value.length, itemRefs.value.length);
}, { deep: true });
</script>

<template>
  <button @click="list.push(list.length + 1)">add</button>
  <ul>
    <li v-for="item in list" ref="items">
      {{ item }}
    </li>
  </ul>
</template>

问题原因

从TypeScript类型定义可以看到,useTemplateRef的返回值是只读的浅引用:

function useTemplateRef<T>(key: string): Readonly<ShallowRef<T | null>>

浅引用只会监听引用本身的替换,不会追踪数组内部元素的增减或变化。因此对itemRefs的watch只会执行一次,即使数组内容随list更新而变化,Vue也无法捕获itemRefs的变化,开启深度监听也无效。

解决方案

方案一:用computed包装触发响应

借助数据源list的响应式,用computed包装itemRefs,让Vue在list变化时重新读取itemRefs的值:

<script setup>
import { ref, useTemplateRef, watch, computed } from 'vue'

const list = ref([1, 2, 3])
const itemRefs = useTemplateRef('items')

// 通过computed关联list的变化,触发对itemRefs的响应
const reactiveItemRefs = computed(() => {
  list.value // 触发list的依赖追踪
  return itemRefs.value
})

watch(reactiveItemRefs, (newRefs) => {
  console.log("itemsRef变化:", newRefs.length);
}, { deep: true })

watch(list, () => {
  console.log("list变化:", list.value.length, itemRefs.value.length);
})
</script>

方案二:手动维护响应式数组

放弃useTemplateRef,改用自定义函数手动收集模板引用,直接维护一个深引用数组:

<script setup>
import { ref, watch } from 'vue'

const list = ref([1, 2, 3])
const itemRefs = ref([])

// 手动收集每个元素的引用
const setItemRef = (el, index) => {
  if (el) {
    itemRefs.value[index] = el
  }
}

watch(itemRefs, (newRefs) => {
  console.log("itemsRef变化:", newRefs.length);
}, { deep: true })

watch(list, () => {
  // 列表更新时重置数组长度,避免残留旧引用
  itemRefs.value = new Array(list.value.length).fill(null)
  console.log("list变化:", list.value.length, itemRefs.value.length);
})
</script>

<template>
  <button @click="list.push(list.length + 1)">add</button>
  <ul>
    <li v-for="(item, index) in list" :ref="el => setItemRef(el, index)">
      {{ item }}
    </li>
  </ul>
</template>

方案三:使用watchEffect自动追踪

利用watchEffect自动追踪依赖,当list变化时自动触发对itemRefs的读取:

<script setup>
import { ref, useTemplateRef, watchEffect } from 'vue'

const list = ref([1, 2, 3])
const itemRefs = useTemplateRef('items')

watchEffect(() => {
  list.value // 追踪list的变化
  console.log("itemsRef当前长度:", itemRefs.value.length);
})
</script>

内容的提问来源于stack exchange,提问作者winwin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 20:32:39