Renderizar Listas
v-for
Podemos utilizar la directiva v-for
para renderizar una lista de elementos basada en un array. La directiva v-for
requiere una sintaxis especial en forma de item in items
, donde items
es el array de datos fuente y item
es un alias para el elemento del array que se está iterando:
js
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
template
<li v-for="item in items">
{{ item.message }}
</li>
Dentro del ámbito de v-for
, las expresiones de la template tienen acceso a todas las propiedades del ámbito padre. Además, v-for
también admite un segundo alias opcional para el índice del elemento actual:
js
const parentMessage = ref('Padre')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
template
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
El ámbito de las variables de v-for
es similar al siguiente JavaScript:
js
const parentMessage = 'Padre'
const items = [
/* ... */
]
items.forEach((item, index) => {
// tiene acceso al ámbito externo `parentMessage`
// pero `item` e `index` sólo están disponibles aquí
console.log(parentMessage, item.message, index)
})
Observa en que el valor de v-for
coincide con la firma de la función del callback forEach
. De hecho, puedes utilizar la desestructuración en el alias del elemento v-for
de forma similar a la desestructuración de los argumentos de la función:
template
<li v-for="{ message } in items">
{{ message }}
</li>
<!-- con alias de índice -->
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
</li>
En el caso de los v-for
anidados, el ámbito también funciona de forma similar a las funciones anidadas. Cada ámbito de v-for
tiene acceso a los ámbitos padre:
template
<li v-for="item in items">
<span v-for="childItem in item.children">
{{ item.message }} {{ childItem }}
</span>
</li>
También puedes utilizar of
como delimitador en lugar de in
, para que se acerque más a la sintaxis de JavaScript para los iteradores:
template
<div v-for="item of items"></div>
v-for
con un Objecto
También puedes utilizar v-for
para iterar a través de las propiedades de un objeto. El orden de iteración se basará en el resultado de llamar a Object.values()
sobre el objeto:
js
const myObject = reactive({
title: 'Cómo hacer listas en Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
template
<ul>
<li v-for="value in myObject">
{{ value }}
</li>
</ul>
También puedes proporcionar un segundo alias para el nombre de la propiedad (también conocido como key o clave):
template
<li v-for="(value, key) in myObject">
{{ key }}: {{ value }}
</li>
Y otra para el índice:
template
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
v-for
con un Rango
v-for
también puede tomar un entero. En este caso se repetirá la template tantas veces, basado en un rango de 1...n
.
template
<span v-for="n in 10">{{ n }}</span>
Observa que aquí n
comienza con un valor inicial de 1
en lugar de 0
.
v-for
en <template>
De forma similar a la etiqueta v-if
, también puedes utilizar una etiqueta <template>
con v-for
para renderizar un bloque de múltiples elementos. Por ejemplo:
template
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
v-for
con v-if
Nota
No es recomendado usar v-if
y v-for
en el mismo elemento debido a la precedencia implícita. Consulta la guía de estilo para más detalles.
Cuando existen en el mismo nodo, v-if
tiene mayor prioridad que v-for
. Esto significa que la condición v-if
no tendrá acceso a las variables del ámbito de v-for
:
template
<!--
Esto arrojará un error porque la propiedad "todo"
no está definida en la instancia.
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
Esto puede arreglarse moviendo v-for
a una etiqueta envolvente <template>
(lo que también es más explícito):
template
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
Manteniendo el Estado con key
Cuando Vue está actualizando una lista de elementos renderizados con v-for
, por defecto utiliza una estrategia de "parche en el lugar". Si el orden de los elementos de datos ha cambiado, en lugar de mover los elementos del DOM para que coincidan con el orden de los elementos, Vue parchará cada elemento en su lugar y se asegurará de que refleje lo que debe ser renderizado en ese índice en particular.
Este modo por defecto es eficiente, pero sólo es adecuado cuando el renderizado de la lista no depende del estado de los componentes hijos o del estado temporal del DOM (por ejemplo, los valores de entrada del formulario).
Para darle a Vue una pista para que pueda rastrear la identidad de cada nodo, y así reutilizar y reordenar los elementos existentes, necesitas proporcionar un atributo key
único para cada elemento:
template
<div v-for="item in items" :key="item.id">
<!-- contenido -->
</div>
Cuando se usa <template v-for>
, la key
debería ser colocada en el contenedor <template>
:
template
<template v-for="todo in todos" :key="todo.name">
<li>{{ todo.name }}</li>
</template>
Nota
key
aquí es un atributo especial que se vincula con v-bind
. No debería confundirse con la variable clave de la propiedad cuando se utiliza v-for
con un objeto.
Se recomienda proporcionar un atributo key
con v-for
siempre que sea posible, a menos que el contenido del DOM iterado sea simple (es decir, que no contenga componentes o elementos del DOM con estado), o que se confíe intencionadamente en el comportamiento por defecto para mejorar el rendimiento.
El enlace key
espera valores primitivos, es decir, cadenas y números. No utilices objetos como claves de v-for
. Para un uso detallado del atributo key
, por favor consulta la documentación de la API key
.
v-for
con un Componente
Esta sección supone el conocimiento de Componentes. Siéntete libre de saltarla y volver más tarde.
Puedes usar directamente v-for
en un componente, como cualquier elemento normal (no olvides proporcionar una key
):
template
<MyComponent v-for="item in items" :key="item.id" />
Sin embargo, esto no pasará automáticamente ningún dato al componente, porque los componentes tienen ámbitos aislados propios. Para pasar los datos iterados al componente, debemos usar también props:
template
<MyComponent
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
/>
La razón por la que no se inyecta automáticamente item
en el componente es porque eso hace que el componente esté estrechamente acoplado a cómo funciona v-for
. Ser explícito sobre la procedencia de sus datos hace que el componente sea reutilizable en otras situaciones.
Mira este ejemplo de una simple lista de tareas para ver cómo renderizar una lista de componentes usando v-for
, pasando diferentes datos a cada instancia.
Detección de Cambios en Arrays
Métodos de Mutación
Vue es capaz de detectar cuando se llama a los métodos de mutación de un array reactivo y desencadenar las actualizaciones necesarias. Estos métodos de mutación son:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
Replacing an Array
Los métodos de mutación, como su nombre sugiere, mutan el array original sobre el que son llamados. En comparación, también hay métodos que no mutan, como filter()
, concat()
y slice()
, los cuales no mutan el array original sino que siempre devuelven un nuevo array. Cuando se trabaja con métodos que no mutan, debemos sustituir el array antiguo por el nuevo:
js
// `item` es una ref con valor de array
items.value = items.value.filter((item) => item.message.match(/Foo/))
Podrías pensar que esto hará que Vue tire el DOM existente y vuelva a renderizar toda la lista; afortunadamente, no es el caso. Vue implementa algunas heurísticas inteligentes para maximizar la reutilización de elementos del DOM, por lo que reemplazar un array con otro array que contenga objetos superpuestos es una operación muy eficiente.
Visualizar Resultados Filtrados/Ordenados
A veces queremos mostrar una versión filtrada u ordenada de un array sin mutar o reiniciar los datos originales. En este caso, puedes crear una propiedad computada que retorne el array filtrado u ordenado.
Por ejemplo:
js
const numbers = ref([1, 2, 3, 4, 5])
const evenNumbers = computed(() => {
return numbers.value.filter((n) => n % 2 === 0)
})
template
<li v-for="n in evenNumbers">{{ n }}</li>
En situaciones en las que las propiedades computadas no son factibles (por ejemplo, dentro de bucles v-for
anidados), puede utilizar un método:
js
const sets = ref([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]
])
function even(numbers) {
return numbers.filter((number) => number % 2 === 0)
}
template
<ul v-for="numbers in sets">
<li v-for="n in even(numbers)">{{ n }}</li>
</ul>
¡Ten cuidado con reverse()
y sort()
en una propiedad computada! Estos dos métodos mutarán el array original, lo que debería ser evitado en los getters computados. Crea una copia del array original antes de llamar a estos métodos:
diff
- return numbers.reverse()
+ return [...numbers].reverse()