Komponentenaufteilung

Die aktuelle App.vue ist voll funktionsfähig, aber der gesamte Code ist zusammengepfercht.

Als Nächstes werden wir sie in Komponenten aufteilen. Sie werden lernen:

  • Wie man hochgradig wiederverwendbare untergeordnete Komponenten in der Tailwind-v4-Umgebung schreibt.
  • Wie man defineProps und defineEmits verwendet, um Daten zwischen JS-Komponenten weiterzugeben.

Jetzt insrc/componentserstellen Sie die folgenden vier Dateien im Verzeichnis:

  • TaskHeader.vue(Reine Anzeige)
  • TaskInput.vue(Eingabelogik)
  • TaskFilter.vue(Zustandswechsel)
  • TaskItem.vue(Einzelne Aufgabe)

TaskHeader.vue – Statische Anzeigekomponente

Diese Komponente benötigt keinerlei JS-Logik; sie zeigt nur die UI an.

In Tailwind v4 verwenden wir weiterhin die neue Gradient-Syntax.

Beispiel

<template>
  <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 Hands-On</p>
  </header>
</template>

TaskInput.vue – Verwenden von defineEmits zum Senden von Daten

Diese Komponente ist dafür verantwortlich, Benutzereingaben zu erhalten. Wenn der Benutzer auf „Hinzufügen“ klickt, ändert sie die Daten nicht direkt; stattdessen benachrichtigt sie die übergeordnete Komponente über emit.

Beispiel

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

// Definiere den Eventnamen, den die Komponente emittiert
const emit = defineEmits(['add-task']);

const title = ref('');

const handleSubmit = () => {
  const value = title.value.trim();
  if (!value) return;
 
// Das Event ausgeben und Daten als Argument übergeben
  emit('add-task', value);
  title.value = '';
};
</script>

<template>
  <div class="flex gap-2 mb-8">
    <input
     v-model="title"
     @keyup.enter="handleSubmit"
     placeholder="Was muss heute erledigt werden?"
     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="handleSubmit" class="bg-brand text-white px-6 rounded-2xl font-bold hover:scale-105 active:scale-95 transition-all">
      +
    </button>
  </div>
</template>

TaskFilter.vue – v-model-Kommunikation zwischen Komponenten

In Vue 3.4+ wird empfohlen, defineModel() für die bidirektionale Bindung zu verwenden. Dies ist sehr effizient für das Umschalten der UI in Tailwind v4.

Beispiel

<script setup>
// v-model definieren; der von der übergeordneten Komponente übergebene Wert wird automatisch synchronisiert
const modelValue = defineModel();

const filters = ['all', 'active', 'completed'];
</script>

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

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

TaskItem.vue – Verwenden von defineProps zum Empfangen von Daten

Es ist die zentrale Kindkomponente, zuständig für die Anzeige einer einzelnen Aufgabe und die Meldung von Änderungs-/Löschaktionen.

Beispiel

<script setup>
// Empfange das von der übergeordneten Komponente übergebene task-Objekt
defineProps({
  task: {
    type: Object,
    required: true
  }
});

// Definiere Events, um die übergeordnete Komponente zu benachrichtigen
const emit = defineEmits(['toggle', 'remove']);
</script>

<template>
  <li 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="emit('toggle', 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="emit('remove', task.id)" class="opacity-0 group-hover:opacity-100 text-slate-300 hover:text-red-500 transition-all p-1">
      ✕
    </button>
  </li>
</template>

Refaktorierte App.vue (Logik-Hub)

Die refaktorierte App.vue wird zu einem sauberen Kommandanten, der nur für die Verwaltung der Rohdaten und die Verarbeitung von Anweisungen aus untergeordneten Komponenten verantwortlich ist.

Beispiel

<script setup>
import { ref, computed } from 'vue';
// Untergeordnete Komponenten importieren
import TaskHeader from './components/TaskHeader.vue';
import TaskInput from './components/TaskInput.vue';
import TaskFilter from './components/TaskFilter.vue';
import TaskItem from './components/TaskItem.vue';

// --- Datenzustand ---
const tasks = ref([
{ id: '1', title: 'Neue Funktionen von Tailwind v4 entdecken', isCompleted: false },
{ id: '2', title: 'Composition API meistern', isCompleted: true }
]);
const filter = ref('all');

// --- Logikverarbeitung ---
const handleAddTask = (title) => {
  tasks.value.unshift({ id: crypto.randomUUID(), title, isCompleted: false });
};

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

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

// --- Berechnete Eigenschaften ---
const filteredTasks = computed(() => {
  if (filter.value === 'active') return tasks.value.filter(t => !t.isCompleted);
  if (filter.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">
     
      <TaskHeader />

      <main class="p-6">
        <TaskInput @add-task="handleAddTask" />

        <TaskFilter v-model="filter" />

        <ul class="space-y-3">
          <TransitionGroup name="list">
            <TaskItem
             v-for="task in filteredTasks"
             :key="task.id"
             :task="task"
             @toggle="handleToggleTask"
             @remove="handleRemoveTask"
           />
          </TransitionGroup>
        </ul>

        <div v-if="filteredTasks.length === 0" class="text-center py-12 text-slate-400 text-sm">
Keine zugehörigen Aufgaben...
        </div>
      </main>
    </div>
  </div>
</template>

<style scoped>
.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>

Zusammenfassung der Kernwissenspunkte

defineProps(Von Eltern zu Kind):

  • TaskItemDurchpropsAufgabendaten empfangen. In Vue sind Propsschreibgeschützt. Kindkomponenten sollten niemals direktpropsden Wert ändern. Dies ist der "einseitige Datenfluss".

defineEmits(Von Kind zu Eltern):

  • Wenn eine Kindkomponente Daten ändern muss (z. B. löschen, abhaken), verwendet sie,emitum ein Signal auszusenden, und die Elternkomponente führt die eigentliche Änderungslogik aus. Dadurch gibt es nur eine Quelle für Datenänderungen, was das Debuggen erleichtert.

defineModel(Magisches Werkzeug für Zwei-Wege-Bindung):

  • InTaskFilterDarin haben wir die in Vue 3.4 eingeführte verwendet.defineModelEs vereinfacht den Code für die Zustandssynchronisierung zwischen Eltern- und Kindkomponenten erheblich und erübrigt das manuelle Schreibenpropsund abzuhören@update:modelValue。

Wiederverwendbarkeit von Tailwind v4:

  • Selbst nach dem Aufteilen von Komponenten wirken die atomaren Klassennamen von Tailwind weiterhin. Denn das Vite-Plugin automatischsrcalle unter.vueDateien scannt und die entsprechenden Stile generiert.
Andere Erweiterungen