Directiva v-for (bucles)

La directiva v-for es muy interesante para crear estructuras repetitivas de código HTML de una forma sencilla y sin que el código resulte excesivamente complejo (sobre todo en estructuras que se repiten muchas veces).

Directiva v-for (Vue.js)

La estructura de un v-for es muy sencilla y se basa en la posibilidad de crear un bucle for desde las templates de código HTML de Vue. Teniendo en cuenta su sintaxis, puedes crear código complejo en muy pocas lineas, basado en el bucle forEach de Javascript:

const array = ["Gato", "Perro", "Pájaro", "Dinosaurio"];

// Sintaxis simple (sólo necesitamos el item)
array.forEach(item => console.log(item));

// Sintaxis compleja (necesitamos el índice y el item)
array.forEach((item, index) => console.log(index, item));

Así pues, si en Vue aplicamos una directiva v-for en nuestro código HTML, la sintaxis quedaría algo similar a que veremos a continuación. La variable item es una variable local del bucle que tendrá el valor de cada ítem del array en cada iteración:

<template>
  <div>
    <div v-for="item in array">{{ item }}</div>
  </div>
</template>

<script>
export default {
  name: "BaseBlock",
  data() {
    return {
      array: ["HTML", "CSS", "Javascript", "Terminal"],
    };
  },
};
</script>

En el ejemplo anterior se debe añadir también la directiva :key, que explicaremos a continuación. Se trata de otra directiva en la que profundizaremos más adelante, pero primero vamos a ver su finalidad junto al v-for.

La directiva :key

Aunque el código anterior es teóricamente correcto, si intentamos ejecutarlo, probablemente obtendremos un error del linter en el cuál se nos avisa que se esperaba una directiva v-bind:key junto al v-for y no se ha encontrado:

Elements in iteration expect to have 'v-bind:key' directives

Aún no hemos visto las directivas v-bind, pero en este caso, básicamente se nos pide identificar de forma única cada elemento de un array, y nos pide que lo asociemos con una clave que sea única. Por lo tanto, lo correcto sería aplicar a todo elemento que lleve un v-for, un atributo :key al que se le asigne un valor único del ítem.

Observa también que nosotros podríamos querer que el array tuviera valores repetidos (no es el caso), por lo que quizás el propio nombre del ítem no sea recomendable usarlo como clave. Lo ideal sería utilizar el número del ítem, es decir, su posición en el array. Para eso, modificamos el v-for como se ve a continuación:

<template>
  <div>
    <div v-for="(item, index) in array" :key="index">{{ index }}. {{ item }}</div>
  </div>
</template>

Ahora si debería funcionarnos correctamente, ya que esto permite que en cada iteración item tenga el valor del elemento (HTML, CSS, Javascript...) e index tenga la posición en el array (0, 1, 2...). Finalmente, el navegador obtendría esto:

<div>
  <div>0. HTML</div>
  <div>1. CSS</div>
  <div>2. Javascript</div>
  <div>3. Terminal</div>
</div>

OJO: Ten en cuenta que los índices empiezan a contar en 0. Si quieres que empiecen en uno, es tan fácil como escribir {{ index + 1 }}.

Iterando otros tipos de datos

Con la directiva v-for podemos iterar varios tipos de datos (en general cualquiera que sea iterable), pero si los esquematizamos tenemos los siguientes, los cuales cada uno tiene ciertas particularidades.

Por ejemplo, observa que al igual que con el .forEach() podemos utilizar la sintaxis básica (si sólo usaremos el ítem) o la sintaxis compleja (si necesitamos, por ejemplo, el número de iteración):

Ejemplo Sintaxis avanzada Descripción
v-for="item in array" (item, index) Iteramos por cada uno de los elementos del array.
v-for="item in object" (item, name, index) Iteramos por cada una de las propiedades del objeto.
v-for="i in number" (item, index) Iteramos en un bucle de 1 al número number.
v-for="char in string" (item, index) Iteramos por cada carácter del string.

Hasta ahora hemos visto sólo el de los , pero es posible utilizar . En este caso iteramos por cada una de las propiedades del objeto, pudiendo incluso con la sintaxis avanzada obtener el item, el nombre de la propiedad (name) y su índice (index).

Si en lugar de un indicamos un , el v-for iterará por cada uno de los carácteres que forma el string, como si fuera un array de carácteres. Lo cual también puede tener casos de uso interesantes.

Por último, en el caso de los , tendríamos un caso particular que muchas veces necesitamos iterar desde 1 hasta un número. Podemos hacerlo así:

<template>
  <div>
    <div v-for="i in 10" :key="i">{{ i }} - {{ array[i] }}</div>
  </div>
</template>

<script>
export default {
  name: "BaseBlock",
  data() {
    return {
      array: ["HTML", "CSS", "Javascript", "Terminal"],
    };
  },
};
</script>

Podemos reemplazar v-for="i in 10" por v-for="(i, index) in 10" y en este caso tendremos una variable i que itera de 1..10 y una variable index que itera de 0..9. Esto puede ser útil para ciertas operaciones o bucles específicos.

No mezclar v-for con v-if

Hay que tener mucho cuidado a la hora de utilizar directivas v-for en combinación con directivas v-if, puesto que no son compatibles. Si lo intentamos, obtendremos este error del linter:

The array variable inside 'v-for' directive should be replaced with a computed property that returns filtered array instead. You should not mix 'v-for' with 'v-if'

Hay dos formas sencillas de solucionarlo:

  • Añadir un etiqueta dentro/fuera del v-for con el v-if y así evitar mezclarlos (más simple).
  • Crear una propiedad computada que filtre los resultados (más eficiente/reutilizable).

Imaginemos que queremos mostrar sólo los resultados impares de array. La primera solución podría ser algo similar a esto:

<div v-for="(item, index) in array" :key="item">
  <div v-if="index % 2 === 0">{{ index }}. {{ item }}</div>
</div>

La segunda solución, un poco más compleja pero quizás algo más limpia, podría ser crear una propiedad computada. Esto nos permitiría gestionarlo a través de Vue y reutilizarlo si hay más casos en las que necesitemos la información filtrada. Quedaría algo similar a esto:

<template>
  <div>
    <div v-for="(item, index) in filteredArray" :key="item">
      {{ index }}. {{ item }}
    </div>
  </div>
</template>

<script>
export default {
  name: "BaseBlock",
  data() {
    return {
      array: ["HTML", "CSS", "Javascript", "Terminal"],
    };
  },
  computed: {
    filteredArray() {
      return this.array.filter((item, index) => index % 2 === 0);
    },
  },
};
</script>

Es importante darse cuenta que la propiedad computada filteredArray obtiene el array original y devuelve uno donde se filtran los ítems que nos interesan. Estos valores quedarán cacheados por Vue y se reutilizarán sin volverse a calcular, siempre que el array original no cambie.

Manz
Publicado por Manz

Docente, divulgador informático y freelance. Autor de Emezeta.com, es profesor en la Universidad de La Laguna y dirige el curso de Programación web FullStack y Diseño web FrontEnd de EOI en Tenerife (Canarias). En sus ratos libres, busca GIF de gatos en Internet.