Saltar al contenido

Renderizado de Listas

v-for

Podemos usar la directiva v-for para renderizar una lista de elementos basándose en un array. La directiva v-for requiere una sintaxis especial en la forma 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' }])
js
data() {
  return {
    items: [{ message: 'Foo' }, { message: 'Bar' }]
  }
}
template
<li v-for="item in items">
  {{ item.message }}
</li>

Dentro del ámbito de v-for, las expresiones de template tienen acceso a todas las propiedades del ámbito padre. Además, v-for también soporta un segundo alias opcional para el índice del elemento actual:

js
const parentMessage = ref('Padre')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
js
data() {
  return {
    parentMessage: 'Padre',
    items: [{ message: 'Foo' }, { message: 'Bar' }]
  }
}
template
<li v-for="(item, index) in items">
  {{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
  • Padre - 0 - Foo
  • Padre - 1 - Bar
  • 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` solo están disponibles aquí.
      console.log(parentMessage, item.message, index)
    })

    Observa cómo el valor de v-for coincide con la firma de la función del callback forEach. De hecho, puedes usar la desestructuración en el alias de elemento de v-for de forma similar a la desestructuración de argumentos de función:

    template
    <li v-for="{ message } in items">
      {{ message }}
    </li>
    
    <!-- con alias del índice -->
    <li v-for="({ message }, index) in items">
      {{ message }} {{ index }}
    </li>

    Para 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 usar of como delimitador en lugar de in, para que se acerque más a la sintaxis de JavaScript para iteradores:

    template
    <div v-for="item of items"></div>

    v-for con un Objeto

    También puedes usar 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: '10-04-2016'
    })
    js
    data() {
      return {
        myObject: {
          title: 'Cómo hacer listas en Vue',
          author: 'Jane Doe',
          publishedAt: '10-04-2016'
        }
      }
    }
    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):

    template
    <li v-for="(value, key) in myObject">
      {{ key }}: {{ value }}
    </li>

    Y otro para el índice:

    template
    <li v-for="(value, key, index) in myObject">
      {{ index }}. {{ key }}: {{ value }}
    </li>

    [Pruébalo en el Playground](https://play.vuejs.org/#eNo9jjFvgzAQhf/KE0sSCQKpqg7IqRSpQ9WlWycvBC6KW2NbcKaNEP+9B7Tx4nt33937Y3IKYT9ESspE9XVnAqMnjuFZO9MG3zFGdFTVbAbChEvnW2yE32inXe1dz2hv7+dPqhnHO7kdtQPYsKUSm1f/DfZoPKzpuYdx+JAL6cxU provisional+E+o provisional/9cO8SzslZoTy+yhODxlxWN2KMR22mmn8jWrpBTB1AZbMc2KVbTyQ56yBkN28d1RJ9uhspFSfNEtFf+GfnZzjP/oOll2NQPjuM4xTftZyIaU5VwuN0SsqMqtWZxUvliq/J4jmX4BTCp08A==)

    v-for con un Rango

    v-for también puede tomar un entero. En este caso, repetirá el template esa cantidad de veces, basándose en un rango de 1...n.

    template
    <span v-for="n in 10">{{ n }}</span>

    Ten en cuenta que aquí n comienza con un valor inicial de 1 en lugar de 0.

    v-for en <template>

    Similar a el template v-if, también puedes usar 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

    Cuando existen en el mismo nodo, v-if tiene una prioridad más alta que v-for. Esto significa que la condición v-if no tendrá acceso a las variables del ámbito de v-for:

    template
    <!--
    Esto generará 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 se puede solucionar moviendo v-for a una etiqueta <template> de envoltura (lo cual también es más explícito):

    template
    <template v-for="todo in todos">
      <li v-if="!todo.isComplete">
        {{ todo.name }}
      </li>
    </template>

    Nota

    No se recomienda usar v-if y v-for en el mismo elemento debido a la precedencia implícita.

    Hay dos casos comunes en los que esto puede ser tentador:

    • Para filtrar elementos en una lista (por ejemplo, v-for="user in users" v-if="user.isActive"). En estos casos, reemplaza users con una nueva propiedad computada que devuelva tu lista filtrada (por ejemplo, activeUsers).

    • Para evitar renderizar una lista si debe estar oculta (por ejemplo, v-for="user in users" v-if="shouldShowUsers"). En estos casos, mueve el v-if a un elemento contenedor (por ejemplo, ul, ol).

    Mantener el Estado con key

    Cuando Vue está actualizando una lista de elementos renderizados con v-for, por defecto utiliza una estrategia de "parcheo in situ". 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 parcheará cada elemento in situ y se asegurará de que refleje lo que debería renderizarse en ese índice particular.

    Este modo predeterminado es eficiente, pero solo es adecuado cuando la salida del renderizado de tu lista no depende del estado del componente hijo o del estado temporal del DOM (por ejemplo, valores de entrada de formularios).

    Para darle una pista a Vue y que pueda rastrear la identidad de cada nodo, y así reutilizar y reordenar los elementos existentes, debes 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 debe colocarse en el contenedor <template>:

    template
    <template v-for="todo in todos" :key="todo.name">
      <li>{{ todo.name }}</li>
    </template>

    Nota

    Aquí key es un atributo especial que se vincula con v-bind. No debe confundirse con la variable clave de propiedad al usar 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, no contenga componentes o elementos del DOM con estado), o si dependes intencionalmente del comportamiento predeterminado para obtener mejoras de rendimiento.

    La vinculación de key espera valores primitivos, es decir, cadenas y números. No uses objetos como v-for keys. Para un uso detallado del atributo key, consulta la documentación de la API de key.

    v-for con un Componente

    Esta sección asume conocimientos de Componentes. No dudes en omitirla 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, también deberíamos usar props:

    template
    <MyComponent
      v-for="(item, index) in items"
      :item="item"
      :index="index"
      :key="item.id"
    />

    La razón para no inyectar automáticamente item en el componente es porque eso hace que el componente esté fuertemente acoplado a cómo funciona v-for. Ser explícito sobre el origen de sus datos hace que el componente sea reutilizable en otras situaciones.

    Mira este ejemplo de una lista de tareas simple para ver cómo renderizar una lista de componentes usando v-for, pasando diferentes datos a cada instancia.

    Mira este ejemplo de una lista de tareas simple 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 cuándo se llaman los métodos de mutación de un array reactive y disparar las actualizaciones necesarias. Estos métodos de mutación son:

    • push()
    • pop()
    • shift()
    • unshift()
    • splice()
    • sort()
    • reverse()

    Reemplazando un Array

    Los métodos de mutación, como su nombre indica, mutan el array original sobre el que se llaman. En comparación, también existen métodos no mutables, por ejemplo, filter(), concat() y slice(), que no mutan el array original pero siempre devuelven un nuevo array. Al trabajar con métodos no mutables, debemos reemplazar el array antiguo con el nuevo:

    js
    // `items` es una referencia con valor de array
    items.value = items.value.filter((item) => item.message.match(/Foo/))
    js
    this.items = this.items.filter((item) => item.message.match(/Foo/))

    Podrías pensar que esto hará que Vue descarte el DOM existente y vuelva a renderizar toda la lista - afortunadamente, no es así. 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.

    Mostrar Resultados Filtrados/Ordenados

    A veces queremos mostrar una versión filtrada o ordenada de un array sin mutar o restablecer los datos originales. En este caso, puedes crear una propiedad computada que devuelva el array filtrado o ordenado.

    Por ejemplo:

    js
    const numbers = ref([1, 2, 3, 4, 5])
    
    const evenNumbers = computed(() => {
      return numbers.value.filter((n) => n % 2 === 0)
    })
    js
    data() {
      return {
        numbers: [1, 2, 3, 4, 5]
      }
    },
    computed: {
      evenNumbers() {
        return this.numbers.filter(n => n % 2 === 0)
      }
    }
    template
    <li v-for="n in evenNumbers">{{ n }}</li>

    En situaciones donde las propiedades computedas no son factibles (por ejemplo, dentro de bucles v-for anidados), puedes usar 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)
    }
    js
    data() {
      return {
        sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
      }
    },
    methods: {
      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 cual debe evitarse en los getters computados. Crea una copia del array original antes de llamar a estos métodos:

    diff
    - return numbers.reverse()
    + return [...numbers].reverse()
    Renderizado de Listas
    CYBER MONDAY
    Get Official Certification for 60% OFF
    Get Certified
    00
    hours
    :
    12
    minutes
    :