Gostaria de reagir a alterações feitas em um array de elementos de referência de template criados com um v-for
loop sobre alguma coleção de objetos arbitrários. Gostaria de passar esse array para um componente / composable e fazê-lo reagir às alterações.
A documentação fornece uma maneira de salvar esses elementos em uma matriz: https://vuejs.org/guide/essentials/template-refs.html#refs-inside-v-for
Modifiquei o exemplo fornecido para ver se o itemsRef
array resultante seria reativo. O código é:
<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>
Mas o valor de retorno de useTemplateRef
parece ser um ShallowRef
, a julgar pela sua assinatura TypeScript:
function useTemplateRef<T>(key: string): Readonly<ShallowRef<T | null>>
Então o watch
on itemRefs
realmente é executado apenas uma vez:
Como você pode ver, o conteúdo do array está mudando ( list
é atualizado), mas as mudanças itemRefs
não são capturadas pelo vue. Eu tentei fazer o watch
deep, mas não ajudou.
Existe uma maneira de fazer o ref não ser superficial de alguma forma? Ou talvez haja outra maneira?
const { createApp, ref, useTemplateRef, watch, markRaw } = Vue;
const app = createApp({
setup() {
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 });
return { list }
}
});
app.mount('#app');
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id="app">
<button @click="list.push(list.length + 1)">add</button>
<ul>
<li v-for="item in list" ref="items">
{{ item }}
</li>
</ul>
</div>
itemRefs
é atualizado (por exemplo, reativo). Mas é sempre atualizado 1 ciclo de renderização apóslist
ter mudado.Envolva seu
console.log
emnextTick
(importado de 'vue') e os números corresponderão:Observe também que você não pode assistir profundamente, pois é uma ref superficial . Para conseguir essa funcionalidade, use um normal
ref
:Usar
const elements = ref([])
withref="elements"
dentro de av-for
não garante ordem, conforme a documentação do vue .Além disso, se você
ref="elements"
for um Componente, oelements
array estará cheio deComponentPublicInstance
elementos.Para combater isso, usei esse gist como base e criei este composable:
Que pode ser usado desta forma:
Agora o
messageRefs
array está totalmente reativo e em sincronia total commessages
o array.