Desarrollo del negocio principal

A continuación, construiremos la lógica central de gestión de tareas en App.vue.

Toda la lógica es la siguiente:

  1. Flujo de datos: el texto ingresado en el campo de entrada ->v-modelActualizaciónnewTaskTitle。
  2. Activación de la acción: presionar Enter -> ejecutaraddTask -> tasksAgregar un elemento al array.
  3. Actualización de la vista:taskscambio -> desencadenafilteredTasksrecálculo automático -> Vue vuelve a renderizarlila lista.
  4. Flujo de estados: hacer clic en el botón de filtro -> modificarcurrentFiltervalor -> volver a activarfilteredTasksel cálculo -> la página muestra tareas de diferentes categorías.

Diseño básico y datos reactivos

Escribe en src/App.vue:

Ejemplo

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

// 1. Modelo de datos de tareas
const tasks = ref([
{ id: '1', title: 'Probar las nuevas características de Tailwind v4', isCompleted: false },
{ id: '2', title: 'Dominar Composition API', isCompleted: true }
]);

const newTaskTitle = ref('');
const currentFilter = ref('all');

// 2. Métodos principales
const addTask = () => {
  const title = newTaskTitle.value.trim();
  if (!title) return;
 
  tasks.value.unshift({
    id: crypto.randomUUID(),
    title,
    isCompleted: false
  });
  newTaskTitle.value = '';
};

const removeTask = (id) => {
  tasks.value = tasks.value.filter(t => t.id !== id);
};

const toggleTask = (id) => {
  const task = tasks.value.find(t => t.id === id);
  if (task) task.isCompleted = !task.isCompleted;
};

// 3. Propiedad computada para el filtrado
const filteredTasks = computed(() => {
  if (currentFilter.value === 'active') return tasks.value.filter(t => !t.isCompleted);
  if (currentFilter.value === 'completed') return tasks.value.filter(t => t.isCompleted);
  return tasks.value;
});
</script>

<template>
  <div class="min-h-screen py-12 px-4">
    <div class="max-w-md mx-auto bg-white rounded-3xl shadow-xl shadow-slate-200 border border-slate-100 overflow-hidden">
     
      <header class="bg-linear-to-br from-blue-600 to-indigo-700 p-8 text-white">
        <h1 class="text-3xl font-black tracking-tight">TaskHub</h1>
        <p class="text-blue-100/80 text-sm">Vue 3 + Tailwind v4 en acción</p>
      </header>

      <main class="p-6">
        <div class="flex gap-2 mb-8">
          <input
           v-model="newTaskTitle"
           @keyup.enter="addTask"
           placeholder="¿Qué hay que hacer hoy?"
           class="flex-1 bg-slate-50 border-none rounded-2xl px-4 py-3 focus:ring-2 focus:ring-brand/50 outline-none transition-all"
         />
          <button @click="addTask" class="bg-brand text-white px-6 rounded-2xl font-bold hover:scale-105 active:scale-95 transition-all">
            +
          </button>
        </div>

        <div class="flex p-1 bg-slate-100 rounded-xl mb-6">
          <button
           v-for="f in ['all', 'active', 'completed']"
           :key="f"
           @click="currentFilter = f"
           :class="[
             'flex-1 py-1.5 text-xs font-bold rounded-lg transition-all capitalize',
             currentFilter === f ? 'bg-white text-brand shadow-sm' : 'text-slate-500'
           ]"

         >
            {{ f }}
          </button>
        </div>

        <ul class="space-y-3">
          <TransitionGroup name="list">
            <li
             v-for="task in filteredTasks"
             :key="task.id"
             class="group flex items-center justify-between p-4 bg-slate-50 rounded-2xl border border-transparent hover:border-slate-200 hover:bg-white transition-all"
           >
              <div class="flex items-center gap-3">
                <input
                 type="checkbox"
                 :checked="task.isCompleted"
                 @change="toggleTask(task.id)"
                 class="w-5 h-5 accent-brand cursor-pointer"
               />
                <span :class="['text-slate-700 font-medium', task.isCompleted ? 'line-through text-slate-400 opacity-50' : '']">
                  {{ task.title }}
                </span>
              </div>
              <button @click="removeTask(task.id)" class="opacity-0 group-hover:opacity-100 text-slate-300 hover:text-red-500 transition-all p-1">
                ✕
              </button>
            </li>
          </TransitionGroup>
        </ul>

        <div v-if="filteredTasks.length === 0" class="text-center py-12 text-slate-400 text-sm">
No hay tareas relacionadas...
        </div>
      </main>
    </div>
  </div>
</template>

<style scoped>
/* Animación de transición de lista */
.list-enter-active, .list-leave-active { transition: all 0.4s cubic-bezier(0.18, 0.89, 0.32, 1.28); }
.list-enter-from, .list-leave-to { opacity: 0; transform: translateX(30px); }
</style>

Después de ejecutarlo, la interfaz se ve así:


Conceptos clave

1. API de composición (Composition API)

El código anterior muestra la forma más esencial de organizar la lógica en Vue 3:

  • ref(Fundamentos de reactividad):tasks、newTaskTitleson referencias reactivas. En<script>para modificarlas debes usar.value(por ejemplo,tasks.value = ...), pero en<template>escribes directamente el nombre de la variable. Vue rastrea automáticamente los cambios de estos valores y actualiza la interfaz.
  • computed(Propiedad computada):filteredTasksEs un caso de uso muy clásico. Se basa entasksycurrentFilterse deriva de.
  • Ventaja: es reactiva y tienecachéSitasksno ha cambiado, al renderizar la página varias veces no repetirá la lógica de filtrado.

2. Renderizado de listas y valor de Key

<li v-for="task in filteredTasks" :key="task.id">
  • :keyLa importancia de: en Vue, el bucle de lista debe proporcionarkey. Aquí se utilizacrypto.randomUUID()el ID único generado. Esto garantiza que cuando el orden de la lista cambie o se elimine un elemento, Vue pueda identificar con precisión el nodo DOM específico, evitando errores de renderizado (especialmente en listas con animaciones o estados de entrada).

3. Manejo de eventos y modificadores

  • @keyup.enter: esto es unmodificador de teclado. Hace queaddTaskla función solo se dispare cuando el usuario presiona la tecla Enter, mejorando enormemente la experiencia de entrada.
  • Paso de parámetros en eventos:removeTask(task.id)Muestra cómo pasar a la función los datos del elemento actual del bucle en la plantilla.

4. Enlace bidireccional (v-model)

  • v-model="newTaskTitle"Logra la sincronización entre el campo de entrada y la variable.
  • Principio: en realidad es:valuey@inputazúcar sintáctico de.

5. Sistema de animación (TransitionGroup)

  • Es un componente integrado en Vue, diseñado específicamente para manejar listas (v-for) en su adición y eliminación.
  • Cambia automáticamente las clases CSS cuando los elementos entran o salen (por ejemplo,.list-enter-from). Junto con elstyleal final del código, se consigue el efecto de que los elementos de la lista se deslicen suavemente al entrar y salir.

Explicación de las características de estilo de Tailwind v4

Tailwind v4, en comparación con v3, pone más énfasis enlas capacidades CSS nativasyla simplicidad。

1. Nueva sintaxis de degradados

  • bg-linear-to-br: en v4, los degradados ya no necesitanbg-gradient-to-bresta escritura tan extensa; simplemente se usalinear-to-方位más cercano al CSS nativo.linear-gradient。

2. Manejo de opacidad

  • text-blue-100/80:/80representa un 80 % de opacidad. El manejo de la opacidad de los colores en v4 es muy intuitivo, sin necesidad de predefinir configuraciones de color complejas.

3. Referencia a variables del tema

  • bg-brand: En el@themede CSS definimos--color-brand. En v4, basta con definir una variable CSS para que Tailwind genere automáticamente la clase de utilidad correspondiente (por ejemplo,bg-brand, text-brand, border-brand), sin necesidad de lidiar con el archivo de configuración de JS.

4. Potentes pseudo-clases interactivas

  • groupy `group-hover:opacity-100`:
  • Enlise establece engroup。
  • En el botón de eliminar se establecegroup-hover:opacity-100。
  • Efecto: se logra que "el botón de eliminar solo se muestre cuando el ratón se sitúa sobre esta fila". Este tipo decoordinación de estado padre-hijono requiere escribir ni una línea de código JS.
  • active:scale-95: utiliza pseudo-clases CSS para implementar rápidamente la retroalimentación de escalado del botón al hacer clic.

5. Bordes modernos y anillos

  • focus:ring-2 focus:ring-brand/50: cuando el campo de entrada está enfocado, se utilizaringla propiedad para crear un borde con resplandor exterior,brand/50lo que garantiza la transparencia del halo, en lugar de un color sólido rígido.
Otras extensiones