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
相关产品推荐
相关产品推荐

