tutoriales.com

Controlando la Visibilidad: Directivas v-if, v-show y v-for en Vue 3 para Renderizado Condicional y Listas

Este tutorial profundiza en las directivas fundamentales de Vue 3: `v-if`, `v-show` y `v-for`. Dominarás el renderizado condicional para mostrar u ocultar elementos de la interfaz, y aprenderás a generar listas dinámicas de manera eficiente.

Intermedio15 min de lectura5 views
Reportar error

🚀 Introducción al Renderizado Dinámico en Vue 3

En el desarrollo de aplicaciones web modernas, la capacidad de mostrar u ocultar elementos de la interfaz de usuario (UI) de forma condicional y de renderizar listas de datos dinámicamente es fundamental. Vue.js, con su enfoque reactivo y sus directivas intuitivas, facilita enormemente estas tareas.

Este tutorial te guiará a través de tres directivas esenciales en Vue 3 que te permitirán controlar la visibilidad y el renderizado de elementos: v-if, v-show y v-for. Comprender sus diferencias y cuándo usar cada una te ayudará a construir interfaces de usuario más eficientes, robustas y reactivas.

¿Por qué son importantes estas directivas? 🤔

Las aplicaciones web raramente son estáticas. Necesitan reaccionar a los datos, a las interacciones del usuario y al estado de la aplicación. Aquí es donde entra el renderizado dinámico:

  • v-if y v-show: Permiten que tu UI se adapte al estado. Por ejemplo, mostrar un mensaje de error solo si hay un error, o un botón de "Guardar" solo si el usuario tiene permisos.
  • v-for: Esencial para mostrar colecciones de datos, como una lista de productos, comentarios, usuarios o elementos de un menú, sin tener que escribir el mismo HTML repetidamente.

Dominar estas directivas es un paso clave para convertirte en un desarrollador Vue competente. ¡Empecemos!


🚦 v-if vs. v-show: Renderizado Condicional

Vue 3 ofrece dos directivas principales para el renderizado condicional: v-if y v-show. Ambas parecen lograr el mismo objetivo (mostrar u ocultar un elemento), pero lo hacen de maneras fundamentalmente diferentes, lo que tiene implicaciones en el rendimiento y la flexibilidad.

v-if: Destrucción y Recreación del DOM 💥

La directiva v-if es un mecanismo de renderizado condicional "real". Cuando la condición asociada a v-if es false, el elemento y sus componentes hijos son completamente eliminados del DOM. Cuando la condición se vuelve true, el elemento (y sus hijos) son recreados e insertados en el DOM.

¿Cuándo usar v-if? 🎯

  • Cuando necesitas alternar elementos con poca frecuencia. La sobrecarga de creación/destrucción puede ser significativa si el cambio es constante.
  • Cuando necesitas controlar la lógica de montaje y desmontaje de componentes, ya que v-if activa los hooks de ciclo de vida (mounted, unmounted).
  • Cuando el elemento es pesado o tiene muchos recursos asociados, y quieres evitar que ocupe espacio o consuma recursos si no es visible.
  • Para renderizar bloques condicionales mutuamente excluyentes con v-else y v-else-if.

Ejemplo de v-if

<template>
  <div>
    <button @click="toggleMessage">Alternar Mensaje</button>
    <p v-if="showMessage">¡Hola, soy un mensaje que aparece y desaparece del DOM!</p>
    <p v-else>El mensaje está oculto.</p>

    <h2>Ejemplo con v-if, v-else-if, v-else</h2>
    <input type="number" v-model="score" placeholder="Introduce una puntuación">
    <p v-if="score >= 90">¡Excelente trabajo! 🏆</p>
    <p v-else-if="score >= 70">Buen trabajo. 👍</p>
    <p v-else-if="score >= 50">Puedes mejorar. 😐</p>
    <p v-else>Necesitas practicar más. 😥</p>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const showMessage = ref(true);
const score = ref(0);

const toggleMessage = () => {
  showMessage.value = !showMessage.value;
};
</script>
💡 Consejo: `v-if` puede ser usado en un `
¿Necesito remover/recrear el elemento del DOM? v-if No ¿Se alternará frecuentemente? v-show No

🔄 v-for: Renderizando Listas Dinámicas

La directiva v-for es la herramienta de Vue para renderizar una lista de elementos basados en un array de datos. Es un bucle potente y eficiente que te permite transformar una colección de datos en elementos de UI repetidos.

Sintaxis Básica de v-for 📖

La sintaxis más común de v-for es item in items, donde items es el array de datos y item es una variable que representa el elemento actual en cada iteración.

<template>
  <div>
    <h2>Lista de Tareas</h2>
    <ul>
      <li v-for="todo in todos" :key="todo.id">
        {{ todo.text }}
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const todos = ref([
  { id: 1, text: 'Aprender Vue 3' },
  { id: 2, text: 'Construir una aplicación' },
  { id: 3, text: 'Dominar las directivas' }
]);
</script>

La Importancia de :key 🔑

Cuando usas v-for, es obligatorio proporcionar una key única a cada elemento. La key ayuda a Vue a identificar de forma única cada nodo de la lista, lo que permite a Vue reutilizar y reordenar eficientemente los elementos existentes en lugar de renderizarlos de nuevo. Esto es crucial para el rendimiento y para mantener el estado interno de los componentes.

⚠️ Advertencia: No uses el índice del array (`index`) como `:key` si el orden de los elementos en el array puede cambiar, o si se pueden añadir/eliminar elementos en medio de la lista. Esto puede llevar a errores de rendimiento o de estado. Solo usa el índice si la lista es estática.

Accediendo al Índice y al Objeto

v-for también permite acceder al índice de la iteración, así como iterar sobre propiedades de objetos.

Iterando con índice: (item, index) in items

<template>
  <div>
    <h2>Productos</h2>
    <ul>
      <li v-for="(product, index) in products" :key="product.id">
        {{ index + 1 }}. {{ product.name }} - ${{ product.price }}
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const products = ref([
  { id: 'a1', name: 'Laptop', price: 1200 },
  { id: 'b2', name: 'Teclado', price: 75 },
  { id: 'c3', name: 'Mouse', price: 30 }
]);
</script>

Iterando sobre un objeto: (value, key, index) in object

<template>
  <div>
    <h2>Detalles del Usuario</h2>
    <ul>
      <li v-for="(value, key, index) in user" :key="key">
        {{ index + 1 }}. {{ key }}: {{ value }}
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const user = ref({
  firstName: 'Juan',
  lastName: 'Pérez',
  age: 30,
  city: 'Madrid'
});
</script>

v-for con Rangos y Strings

También puedes usar v-for con un número para repetir un elemento un número de veces, o con un string para iterar sobre sus caracteres.

Con un rango numérico: n in 10 (iterará 10 veces, n irá de 1 a 10)

<template>
  <div>
    <h2>Números del 1 al 5</h2>
    <ul>
      <li v-for="n in 5" :key="n">Número {{ n }}</li>
    </ul>
  </div>
</template>

Con un String: char in 'Vue'

<template>
  <div>
    <h2>Deletrea Vue</h2>
    <ul>
      <li v-for="char in 'Vue'" :key="char">{{ char }}</li>
    </ul>
  </div>
</template>

v-for en un <template> 🧩

Al igual que v-if, v-for puede ser usado en una etiqueta <template> para renderizar un bloque de múltiples elementos. Esto es útil si no quieres añadir un elemento contenedor extra al DOM.

<template>
  <div>
    <h2>Componentes Reutilizables</h2>
    <template v-for="item in items" :key="item.id">
      <h3>{{ item.title }}</h3>
      <p>{{ item.description }}</p>
      <hr>
    </template>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const items = ref([
  { id: 1, title: 'Item 1', description: 'Descripción del item 1' },
  { id: 2, title: 'Item 2', description: 'Descripción del item 2' }
]);
</script>

Interacciones con v-for 🖱️

A menudo querrás que los elementos de una lista sean interactivos. Puedes adjuntar manejadores de eventos directamente a los elementos iterados.

<template>
  <div>
    <h2>Lista Interactiva</h2>
    <ul>
      <li v-for="user in users" :key="user.id" @click="selectUser(user)" :class="{ 'selected': selectedUser === user }">
        {{ user.name }}
      </li>
    </ul>
    <div v-if="selectedUser">
      <h3>Usuario Seleccionado:</h3>
      <p>Nombre: {{ selectedUser.name }}</p>
      <p>Email: {{ selectedUser.email }}</p>
    </div>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const users = ref([
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' },
  { id: 3, name: 'Charlie', email: 'charlie@example.com' }
]);

const selectedUser = ref(null);

const selectUser = (user) => {
  selectedUser.value = user;
};
</script>

<style scoped>
.selected {
  background-color: #e0f7fa;
  font-weight: bold;
  border-left: 3px solid #00bcd4;
}
li {
  cursor: pointer;
  padding: 5px;
  margin-bottom: 2px;
}
</style>
🔥 Importante: Siempre que sea posible, prefiere usar una propiedad de ID única como `:key` en `v-for`. Esto garantiza la mejor optimización del rendimiento y la estabilidad del estado en tu lista.
Array de Datos v-for en Template Asignar :key (obligatorio) Renderizar cada Item ¿Siguiente ítem? Fin de la lista DOM Actualizado

🤝 Combinando Directivas para UI Avanzadas

Las directivas v-if, v-show y v-for son potentes por sí solas, pero su verdadero poder se manifiesta cuando se combinan para crear interfaces de usuario más complejas y reactivas.

v-if con v-for (Anidado) nesting 🏗️

Como se mencionó, no se debe usar v-if y v-for en el mismo elemento. En su lugar, anida v-if dentro del bucle v-for en un elemento hijo o en un <template> anidado.

Esto te permite iterar sobre una lista y, para cada elemento de esa lista, decidir condicionalmente si mostrar o no ciertos detalles o incluso el elemento completo.

<template>
  <div>
    <h2>Usuarios Activos</h2>
    <ul>
      <template v-for="user in allUsers" :key="user.id">
        <li v-if="user.isActive">
          {{ user.name }} ({{ user.email }})
          <span class="badge green">Activo</span>
        </li>
        <li v-else>
          <!-- Este usuario no se muestra si user.isActive es false -->
        </li>
      </template>
    </ul>

    <h2>Productos con Stock</h2>
    <ul>
      <li v-for="product in products" :key="product.id">
        {{ product.name }}
        <span v-if="product.inStock" class="badge blue">En Stock</span>
        <span v-else class="badge red">Agotado</span>
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const allUsers = ref([
  { id: 1, name: 'Alice', email: 'alice@example.com', isActive: true },
  { id: 2, name: 'Bob', email: 'bob@example.com', isActive: false },
  { id: 3, name: 'Charlie', email: 'charlie@example.com', isActive: true }
]);

const products = ref([
  { id: 'p1', name: 'Teléfono', inStock: true },
  { id: 'p2', name: 'Tablet', inStock: false },
  { id: 'p3', name: 'Smartwatch', inStock: true }
]);
</script>

v-show con v-for 🖼️

Si necesitas alternar la visibilidad de elementos dentro de una lista con mucha frecuencia, v-show es una opción más eficiente que v-if.

<template>
  <div>
    <h2>Elementos Filtrables</h2>
    <input type="text" v-model="searchText" placeholder="Filtrar elementos...">
    <ul>
      <li v-for="item in items" :key="item.id" v-show="item.name.toLowerCase().includes(searchText.toLowerCase())">
        {{ item.name }}
      </li>
    </ul>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const searchText = ref('');
const items = ref([
  { id: 1, name: 'Manzana' },
  { id: 2, name: 'Pera' },
  { id: 3, name: 'Naranja' },
  { id: 4, name: 'Banana' }
]);
</script>

En este ejemplo, todos los elementos de la lista items se renderizan en el DOM, pero solo se muestran aquellos cuyos nombres coinciden con searchText. Esto es ideal para filtros de búsqueda donde la lista completa está presente pero solo se muestran subconjuntos basados en la entrada del usuario.

Casos de Uso Avanzados y Patrones 💡

  • Listas de tareas (To-Do Lists): Usa v-for para renderizar cada tarea y v-if para mostrar un estado diferente (completado/pendiente).
  • Menús dinámicos: v-for para los ítems del menú y v-if/v-show para mostrar submenús o ítems basados en permisos de usuario.
  • Tabs con contenido condicional: Usa v-if para mostrar el contenido de una pestaña activa y ocultar las demás, o v-show si las pestañas cambian muy a menudo y el contenido no es excesivamente complejo.
🤔 ¿Cuál es el mejor enfoque para grandes listas? Para listas muy grandes (cientos o miles de elementos), incluso `v-show` puede ser ineficiente porque todos los elementos están en el DOM. En estos casos, considera técnicas como la virtualización de listas (usando bibliotecas como `vue-virtual-scroller` o `vue-infinite-loading`) para renderizar solo los elementos visibles en la ventana gráfica.
90% Dominio de Directivas

🛠️ Ejercicio Práctico: Un Panel de Administración Simple

Para consolidar tus conocimientos, vamos a construir un panel de administración simple que utilice las tres directivas.

Requisitos:

  1. Mostrar una lista de usuarios.
  2. Cada usuario tendrá un estado (active o inactive).
  3. Permitir filtrar los usuarios para ver solo los activos o solo los inactivos, o todos.
  4. Mostrar un mensaje condicional si no hay usuarios en la lista filtrada.
<template>
  <div class="admin-panel">
    <h2>Panel de Administración de Usuarios</h2>

    <div class="controls">
      <button @click="filterStatus = 'all'" :class="{ 'active-filter': filterStatus === 'all' }">Todos</button>
      <button @click="filterStatus = 'active'" :class="{ 'active-filter': filterStatus === 'active' }">Activos</button>
      <button @click="filterStatus = 'inactive'" :class="{ 'active-filter': filterStatus === 'inactive' }">Inactivos</button>
    </div>

    <ul v-if="filteredUsers.length > 0" class="user-list">
      <li v-for="user in filteredUsers" :key="user.id" class="user-item">
        <span class="user-name">{{ user.name }}</span>
        <span :class="['badge', user.isActive ? 'green' : 'red']">
          {{ user.isActive ? 'Activo' : 'Inactivo' }}
        </span>
        <button @click="toggleUserStatus(user.id)">
          {{ user.isActive ? 'Desactivar' : 'Activar' }}
        </button>
      </li>
    </ul>
    <div v-else class="no-users-message">
      <p>No hay usuarios para mostrar con el filtro actual. 😔</p>
    </div>

  </div>
</template>

<script setup>
import { ref, computed } from 'vue';

const users = ref([
  { id: 1, name: 'Ana García', isActive: true },
  { id: 2, name: 'Carlos López', isActive: false },
  { id: 3, name: 'Elena Ruiz', isActive: true },
  { id: 4, name: 'David Martín', isActive: false },
  { id: 5, name: 'Sofía Romero', isActive: true },
]);

const filterStatus = ref('all'); // 'all', 'active', 'inactive'

const filteredUsers = computed(() => {
  if (filterStatus.value === 'active') {
    return users.value.filter(user => user.isActive);
  } else if (filterStatus.value === 'inactive') {
    return users.value.filter(user => !user.isActive);
  } else {
    return users.value; // 'all'
  }
});

const toggleUserStatus = (id) => {
  const user = users.value.find(u => u.id === id);
  if (user) {
    user.isActive = !user.isActive;
  }
};
</script>

<style scoped>
.admin-panel {
  font-family: Arial, sans-serif;
  max-width: 600px;
  margin: 20px auto;
  padding: 20px;
  border: 1px solid #eee;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

h2 {
  color: #333;
  text-align: center;
  margin-bottom: 20px;
}

.controls {
  text-align: center;
  margin-bottom: 20px;
}

.controls button {
  padding: 8px 15px;
  margin: 0 5px;
  border: 1px solid #007bff;
  border-radius: 5px;
  background-color: #fff;
  color: #007bff;
  cursor: pointer;
  transition: background-color 0.3s ease, color 0.3s ease;
}

.controls button:hover {
  background-color: #e9f5ff;
}

.controls button.active-filter {
  background-color: #007bff;
  color: #fff;
}

.user-list {
  list-style: none;
  padding: 0;
}

.user-item {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 15px;
  margin-bottom: 8px;
  border: 1px solid #ddd;
  border-radius: 5px;
  background-color: #f9f9f9;
}

.user-name {
  flex-grow: 1;
  font-weight: bold;
}

.badge {
  padding: 4px 8px;
  border-radius: 12px;
  font-size: 0.8em;
  color: white;
  margin-left: 10px;
  margin-right: 10px;
}

.badge.green {
  background-color: #28a745;
}

.badge.red {
  background-color: #dc3545;
}

.user-item button {
  padding: 5px 10px;
  border: none;
  border-radius: 5px;
  background-color: #6c757d;
  color: white;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

.user-item button:hover {
  background-color: #5a6268;
}

.no-users-message {
  text-align: center;
  padding: 20px;
  background-color: #fff3cd;
  border: 1px solid #ffeeba;
  border-radius: 5px;
  color: #856404;
}
</style>

Este ejercicio combina:

  • v-for para iterar sobre la lista de users.
  • v-if y v-else para mostrar la lista de usuarios o un mensaje "no hay usuarios".
  • v-if para mostrar el estado activo/inactivo de cada usuario.
  • Propiedades computadas (computed) para filtrar la lista de usuarios de manera reactiva, lo cual es más eficiente que usar v-if directamente en cada <li> para el filtrado general.

🎯 Conclusión

Las directivas v-if, v-show y v-for son pilares fundamentales en el desarrollo de aplicaciones Vue 3. Te permiten construir interfaces de usuario dinámicas que reaccionan a los datos y a las interacciones del usuario de manera eficiente y declarativa.

Al comprender las diferencias entre v-if y v-show, sabrás cuándo es más apropiado destruir/recrear elementos o simplemente ocultarlos con CSS. Y al dominar v-for con el uso correcto de :key, podrás renderizar listas complejas y eficientes con facilidad.

La práctica es clave. Te animo a que experimentes con estos ejemplos y los adaptes a tus propios proyectos. ¡Ahora estás un paso más cerca de dominar el arte del renderizado dinámico en Vue 3!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!