Von Katharina „Kat“ Schmidt & Nova Trent | Java Fleet Systems Consulting

Kat: Frontend-Spezialistin & Mentorin 🎨
Nova: Junior Developer & Lernende 🌟


📚 Was bisher geschah – Vue.js für Anfänger

Bereits veröffentlicht:

  • Teil 1: Setup & Erste API-Anbindung – Vue.js installiert, erste Component, API-Anbindung
  • Teil 2: Styling & Responsive Design – Tailwind CSS, Mobile-First, schöne UI
  • Teil 3: Formulare & User Input – v-model, Forms, POST-Requests, Validation

Heute: Teil 4 fokussiert auf Reaktivität – Das Herzstück von Vue.js verstehen!

Neu in der Serie? Du kannst hier einsteigen, aber die ersten drei Teile geben dir das vollständige Fundament.


⚡ Kurze Zusammenfassung – Das Wichtigste in 30 Sekunden

Dein Problem: Du änderst Daten, aber deine UI updated nicht! WTF?! 😱

Die Lösung: Vue’s Reaktivitäts-System verstehen – wie Vue „weiß“ wann es updaten muss!

Heute lernst du:

  • ✅ Wie Vue Änderungen trackt (Reaktivität erklärt!)
  • ✅ ref() vs reactive() – wann welches?
  • ✅ Arrays manipulieren (push, splice, filter)
  • ✅ Computed Properties (berechnete Werte!)
  • ✅ Watchers (auf Änderungen reagieren)
  • ✅ Warum .value manchmal nervt (aber wichtig ist!)

Dein größter Gewinn: Du verstehst endlich WARUM Vue „magisch“ funktioniert – und wie du die Magie kontrollierst! 🪄

Zeit-Investment: 60-75 Minuten | Schwierigkeit: Mittel (aber wir gehen es langsam an!)


👋 Nova: „Warum updated sich meine Liste NICHT?!“

Hi Leute! Nova hier – und ich bin frustriert! 😤

Letzte Woche haben wir Formulare gebaut. Ich kann Tasks erstellen – super!

ABER wie funktioniert Listen-Manipulation

Heute wollte ich Tasks filtern. „Zeige nur offene Tasks“ – sollte easy sein, oder?

<script setup>
const tasks = ref([]);
const filter = ref('all'); // 'all', 'open', 'completed'

function filterTasks() {
  if (filter.value === 'open') {
    tasks.value = tasks.value.filter(t => !t.completed);
  }
}
</script>

Ich klicke auf „Nur offene anzeigen“ und… NICHTS passiert! 😱

Die Liste bleibt gleich! Keine Änderung!

Nova: „KAT! HILFE! Vue ist kaputt!“ 😭

Kat: [schaut von ihrem Screen auf, lächelt wissend] „Vue ist nicht kaputt. Du verstehst nur noch nicht wie Reaktivität funktioniert.“

Nova: „Reakti… was?!“

Kat: „Reaktivität. Das Herzstück von Vue.js. Setz dich – das wird interessant!“


🪄 Kat: Was ist Reaktivität?

Hey! Kat hier! 👋

Okay, Nova strugglet mit Reaktivität. Das ist NORMAL! Jeder Vue-Anfänger hat diesen Moment.

Lass mich dir eine Geschichte erzählen…

Ohne Reaktivität (Plain JavaScript)

let count = 0;
let doubled = count * 2;

console.log(doubled); // 0

count = 5;
console.log(doubled); // Immer noch 0! 😱

Warum? doubled wird einmal berechnet. Wenn count sich ändert, weiß doubled das nicht!

Du müsstest manuell neu berechnen:

count = 5;
doubled = count * 2; // Manuell neu berechnen!
console.log(doubled); // 10

Das nervt! Du musst immer dran denken!

Mit Reaktivität (Vue.js)

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

const count = ref(0);
const doubled = computed(() => count.value * 2);

console.log(doubled.value); // 0

count.value = 5;
console.log(doubled.value); // 10 automatisch! 🎉
</script>

MAGIC! 🪄 doubled updated sich automatisch!

Nova: „Wait… WIE macht Vue das?!“

Kat: „Gute Frage! Lass mich dir zeigen wie Vue unter der Haube funktioniert…“


🔍 Reaktivität unter der Haube (Simplified!)

Kat zeichnet auf das Whiteboard:

Was passiert bei ref()?

const count = ref(0);

Vue erstellt ein Proxy-Objekt:

// Vereinfacht!
const count = {
  _value: 0, // Der echte Wert
  
  get value() {
    // Wenn jemand count.value liest
    track(); // ← Vue merkt: "Jemand nutzt count!"
    return this._value;
  },
  
  set value(newValue) {
    // Wenn jemand count.value = X schreibt
    this._value = newValue;
    trigger(); // ← Vue sagt: "count hat sich geändert! Updates!"
  }
}

Vue trackt:

  1. GET – Wer liest count.value? → Dependency Tracking
  2. SETcount.value ändert sich? → Trigger Updates!

Nova: „Oh! Das ist wie… Observer Pattern in Java!“

Kat: „EXACTLY! Vue nutzt das Observer-Pattern! Components ‚beobachten‘ Daten. Wenn Daten sich ändern, bekommen Components Bescheid!“


🆚 ref() vs reactive() – Der große Unterschied

Nova fragt: „Ich hab auch von reactive() gehört. Was ist der Unterschied?“

Kat erklärt:

ref() – Für Primitives & Single Values

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

const count = ref(0);
const name = ref('Nova');
const isActive = ref(true);

// Zugriff mit .value!
console.log(count.value); // 0
count.value = 5;
</script>

<template>
  <!-- Im Template KEIN .value! -->
  <p>Count: {{ count }}</p>
</template>

Verwendung:

  • ✅ Primitives (number, string, boolean)
  • ✅ Single values
  • ✅ Arrays
  • ⚠️ Braucht .value im Script!

reactive() – Für Objects

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

const user = reactive({
  name: 'Nova',
  age: 24,
  email: 'nova@example.com'
});

// Kein .value! Direkt zugreifen!
console.log(user.name); // 'Nova'
user.age = 25;
</script>

<template>
  <p>{{ user.name }} - {{ user.age }}</p>
</template>

Verwendung:

  • ✅ Objects mit mehreren Properties
  • ✅ Komplexe Datenstrukturen
  • ❌ Kann nicht reassigned werden!

Der Unterschied visualisiert:

<script setup>
// ref() - Kann reassigned werden ✅
const tasks = ref([]);
tasks.value = []; // OK!
tasks.value = newArray; // OK!

// reactive() - Kann NICHT reassigned werden ❌
const state = reactive({ tasks: [] });
state = {}; // ERROR! ❌
state.tasks = []; // OK! ✅
</script>

Kat’s Faustregel:

Verwende ref() für:

  • Einzelwerte (Primitives)
  • Arrays die du neu zuweisen willst
  • Wenn du dir unsicher bist!

Verwende reactive() für:

  • Objects mit mehreren Properties
  • Wenn du nie das ganze Object neu zuweisen musst

Nova: „Ich bleib erstmal bei ref()… das mit .value ist zwar nervig, aber ich versteh’s besser!“

Kat: „Smart! ref() ist sicherer für Anfänger. Später kannst du reactive() nutzen wenn du willst.“


📋 Arrays manipulieren – Die richtige Art!

Zurück zu Nova’s Problem:

<script setup>
const tasks = ref([
  { id: 1, title: 'Vue lernen', completed: false },
  { id: 2, title: 'Kat danken', completed: true }
]);

// ❌ FALSCH - Nicht reaktiv!
function addTask(task) {
  tasks.push(task); // ERROR! tasks ist kein Array!
}

// ✅ RICHTIG - Mit .value!
function addTask(task) {
  tasks.value.push(task); // ✅
}
</script>

Nova: „Oh! Ich hab .value vergessen! Darum hat’s nicht funktioniert!“

Kat: „Classic! Das passiert JEDEM! Aber jetzt weißt du’s!“ 😄

Alle wichtigen Array-Operationen:

<script setup>
const tasks = ref([]);

// Hinzufügen
function addTask(task) {
  tasks.value.push(task); // Am Ende hinzufügen
  tasks.value.unshift(task); // Am Anfang hinzufügen
}

// Entfernen
function removeTask(id) {
  const index = tasks.value.findIndex(t => t.id === id);
  tasks.value.splice(index, 1); // Element an Index entfernen
}

// Filtern (Neue Array!)
function getOpenTasks() {
  return tasks.value.filter(t => !t.completed);
}

// Mappen (Neue Array!)
function getTaskTitles() {
  return tasks.value.map(t => t.title);
}

// Sortieren
function sortByTitle() {
  tasks.value.sort((a, b) => a.title.localeCompare(b.title));
}

// Alle ändern
function markAllCompleted() {
  tasks.value.forEach(t => {
    t.completed = true;
  });
}

// Array komplett ersetzen
function setTasks(newTasks) {
  tasks.value = newTasks; // Komplett neu zuweisen
}
</script>

Wichtig:

  • .push(), .splice(), .sort() → Mutieren Array direkt → Reaktiv!
  • .filter(), .map() → Geben neues Array zurück → Reaktiv!
  • ❌ Direktes Index-Assignment manchmal problematisch → Besser: .splice()

🧮 Computed Properties – Berechnete Werte!

Kat: „Okay Nova, jetzt wird’s spannend! Computed Properties!“

Nova’s Problem gelöst:

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

const tasks = ref([
  { id: 1, title: 'Vue lernen', completed: false },
  { id: 2, title: 'Kat danken', completed: true },
  { id: 3, title: 'App deployen', completed: false }
]);

const filter = ref('all'); // 'all', 'open', 'completed'

// ✨ Computed Property - Updated automatisch!
const filteredTasks = computed(() => {
  if (filter.value === 'all') {
    return tasks.value;
  }
  if (filter.value === 'open') {
    return tasks.value.filter(t => !t.completed);
  }
  if (filter.value === 'completed') {
    return tasks.value.filter(t => t.completed);
  }
});

// Weitere computed Properties
const openTasksCount = computed(() => {
  return tasks.value.filter(t => !t.completed).length;
});

const completedTasksCount = computed(() => {
  return tasks.value.filter(t => t.completed).length;
});

const allTasksCompleted = computed(() => {
  return tasks.value.length > 0 && 
         tasks.value.every(t => t.completed);
});
</script>

<template>
  <div>
    <!-- Filter Buttons -->
    <div class="flex gap-2 mb-4">
      <button @click="filter = 'all'">
        Alle ({{ tasks.length }})
      </button>
      <button @click="filter = 'open'">
        Offen ({{ openTasksCount }})
      </button>
      <button @click="filter = 'completed'">
        Erledigt ({{ completedTasksCount }})
      </button>
    </div>
    
    <!-- Task List - Updated automatisch! -->
    <div v-for="task in filteredTasks" :key="task.id">
      {{ task.title }}
    </div>
    
    <!-- Success Message -->
    <p v-if="allTasksCompleted" class="text-green-600">
      🎉 Alle Tasks erledigt! Great job!
    </p>
  </div>
</template>

Nova: „ES FUNKTIONIERT! Die Liste filtert sich automatisch!“ 🎉

Kat: „Siehst du! Computed Properties sind MAGIC!“


🆚 Computed vs Methods – Wann was?

Nova fragt: „Warum nicht einfach eine Method?“

<script setup>
// Method
function getFilteredTasks() {
  if (filter.value === 'all') return tasks.value;
  if (filter.value === 'open') return tasks.value.filter(t => !t.completed);
  if (filter.value === 'completed') return tasks.value.filter(t => t.completed);
}

// Computed
const filteredTasks = computed(() => {
  if (filter.value === 'all') return tasks.value;
  if (filter.value === 'open') return tasks.value.filter(t => !t.completed);
  if (filter.value === 'completed') return tasks.value.filter(t => t.completed);
});
</script>

<template>
  <!-- Method - Wird JEDES Mal neu berechnet! -->
  <div v-for="task in getFilteredTasks()" :key="task.id">
    {{ task.title }}
  </div>
  
  <!-- Computed - Wird gecached! -->
  <div v-for="task in filteredTasks" :key="task.id">
    {{ task.title }}
  </div>
</template>

Der Unterschied:

FeatureMethodComputed
BerechnungBei jedem RenderNur wenn Dependencies ändern
Caching❌ Nein✅ Ja!
PerformanceLangsamerSchneller
Use CaseActions, EventsAbgeleitete Werte

Beispiel Performance-Unterschied:

<script setup>
// Method - Wird 3x berechnet (bei jedem Render)!
function expensiveCalculation() {
  console.log('Calculating...'); // Sieht du 3x!
  return tasks.value.filter(t => !t.completed).length;
}

// Computed - Wird 1x berechnet, dann gecached!
const openCount = computed(() => {
  console.log('Calculating...'); // Siehst du 1x!
  return tasks.value.filter(t => !t.completed).length;
});
</script>

<template>
  <!-- Method -->
  <p>{{ expensiveCalculation() }}</p>
  <p>{{ expensiveCalculation() }}</p>
  <p>{{ expensiveCalculation() }}</p>
  
  <!-- Computed -->
  <p>{{ openCount }}</p>
  <p>{{ openCount }}</p>
  <p>{{ openCount }}</p>
</template>

Kat’s Regel:

Computed für:

  • Abgeleitete Werte (filtered lists, counts, etc.)
  • Alles was von reactive Daten abhängt
  • Performance-kritische Berechnungen

Methods für:

  • Event Handler (onClick, onSubmit)
  • Actions die was TUN (nicht nur berechnen)
  • Wenn du Parameter brauchst

Nova: „Ah! Computed ist wie… Lazy Evaluation in Java?“

Kat: „Genau! Oder wie Memoization! Vue cached das Ergebnis!“


👀 Watchers – Auf Änderungen reagieren

Kat: „Jetzt kommt noch ein mächtiges Feature: Watchers!“

Was sind Watchers?

  • „Beobachten“ reaktive Daten
  • Führen Code aus wenn Daten sich ändern
  • Perfekt für Side-Effects!

Beispiel: Auto-Save beim Tippen

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

const taskTitle = ref('');

// Watch - Führt Code aus bei Änderung!
watch(taskTitle, (newValue, oldValue) => {
  console.log(`Titel geändert von "${oldValue}" zu "${newValue}"`);
  
  // Auto-Save zu LocalStorage
  localStorage.setItem('draft-title', newValue);
});
</script>

<template>
  <input v-model="taskTitle" placeholder="Task Titel...">
  <p class="text-sm text-gray-500">
    Auto-saved to localStorage
  </p>
</template>

Beispiel: API-Call bei Filter-Änderung

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

const filter = ref('all');
const tasks = ref([]);
const loading = ref(false);

// Watch filter - Lade Tasks neu bei Änderung
watch(filter, async (newFilter) => {
  loading.value = true;
  
  try {
    const response = await fetch(`/api/tasks?filter=${newFilter}`);
    tasks.value = await response.json();
  } finally {
    loading.value = false;
  }
});
</script>

Beispiel: Validation beim Tippen

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

const email = ref('');
const emailError = ref('');

// Watch email - Validiere während Tippen
watch(email, (newEmail) => {
  if (!newEmail) {
    emailError.value = '';
    return;
  }
  
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(newEmail)) {
    emailError.value = 'Ungültige Email-Adresse';
  } else {
    emailError.value = '';
  }
});
</script>

<template>
  <input v-model="email" type="email">
  <p v-if="emailError" class="text-red-600">{{ emailError }}</p>
</template>

Watch Options (Advanced)

<script setup>
// Immediate - Führe auch beim Mount aus
watch(filter, callback, { immediate: true });

// Deep - Beobachte nested Objects
const user = ref({ name: 'Nova', settings: { theme: 'dark' } });
watch(user, callback, { deep: true });

// Multiple Sources
watch([filter, search], ([newFilter, newSearch]) => {
  console.log('Filter oder Search geändert!');
});
</script>

Nova: „Wow! Watchers sind wie… Lifecycle-Hooks für Daten!“

Kat: „Guter Vergleich! Sie reagieren auf Daten-Lifecycle!“


🎯 Wann welches? Die Entscheidungs-Matrix

Kat erstellt eine Übersicht:

Situation → Tool

"Ich will einen abgeleiteten Wert berechnen"
→ computed()
Beispiel: filteredTasks, openTasksCount

"Ich will auf Änderung reagieren (Side-Effect)"
→ watch()
Beispiel: Auto-Save, Analytics-Tracking, API-Calls

"Ich will eine Action ausführen (User-Interaktion)"
→ Method
Beispiel: submitForm(), deleteTask(), toggleMenu()

"Ich will Daten reaktiv machen"
→ ref() oder reactive()
Beispiel: tasks, filter, loading

Praxis-Beispiele:

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

// ref() - Reaktive Daten
const tasks = ref([]);
const search = ref('');
const sortBy = ref('title');

// computed() - Abgeleitete Werte
const filteredTasks = computed(() => {
  return tasks.value.filter(t => 
    t.title.toLowerCase().includes(search.value.toLowerCase())
  );
});

const sortedTasks = computed(() => {
  return [...filteredTasks.value].sort((a, b) => {
    if (sortBy.value === 'title') {
      return a.title.localeCompare(b.title);
    }
    if (sortBy.value === 'date') {
      return new Date(b.createdAt) - new Date(a.createdAt);
    }
  });
});

// watch() - Side-Effects
watch(search, (newSearch) => {
  // Analytics tracken
  trackEvent('search', { query: newSearch });
  
  // LocalStorage speichern
  localStorage.setItem('lastSearch', newSearch);
});

// Methods - Actions
function deleteTask(id) {
  tasks.value = tasks.value.filter(t => t.id !== id);
}

function markCompleted(id) {
  const task = tasks.value.find(t => t.id === id);
  task.completed = true;
}
</script>

💡 Häufige Reaktivitäts-Fallen (Und wie du sie vermeidest!)

Kat warnt: „Nova, pass auf! Es gibt ein paar Fallen…“

Falle 1: .value vergessen

<script setup>
const count = ref(0);

// ❌ FALSCH
count = 5; // Überschreibt ref-Object! ❌

// ✅ RICHTIG
count.value = 5; // Updated reaktiven Wert ✅
</script>

Fix: Immer .value im <script>! Im <template> automatisch unwrapped.

Falle 2: Array-Index direkt ändern

<script setup>
const tasks = ref([task1, task2, task3]);

// ⚠️ MANCHMAL PROBLEMATISCH
tasks.value[0] = newTask; // Kann Probleme machen

// ✅ BESSER
tasks.value.splice(0, 1, newTask); // Sicher reaktiv
</script>

Fix: Verwende Array-Methods wie .splice(), .push(), .unshift().

Falle 3: Destructuring verliert Reaktivität

<script setup>
const user = reactive({
  name: 'Nova',
  age: 24
});

// ❌ FALSCH - Verliert Reaktivität!
const { name, age } = user;
name = 'Kat'; // Updated NICHT user.name!

// ✅ RICHTIG - Mit toRefs()
import { toRefs } from 'vue';
const { name, age } = toRefs(user);
name.value = 'Kat'; // Updates user.name ✅
</script>

Fix: Verwende toRefs() wenn du destructuren musst.

Falle 4: Reactive Object neu zuweisen

<script setup>
const state = reactive({ count: 0 });

// ❌ FALSCH - Bricht Reaktivität!
state = { count: 5 }; // ERROR!

// ✅ RICHTIG - Properties ändern
state.count = 5; // ✅

// ODER: ref() verwenden
const state = ref({ count: 0 });
state.value = { count: 5 }; // ✅
</script>

Fix: Mit reactive() nur Properties ändern, nicht das ganze Object!

Nova: „Okay, das sind viele Fallen… aber ich merke mir: ref() ist sicherer für mich!“

Kat: „Smart! Genau darum empfehle ich ref() für Anfänger!“


🚀 Praktisches Beispiel: Task-Manager mit Reaktivität

Kat: „Lass uns alles zusammenbringen!“

TaskManager.vue – Complete Component

<template>
  <div class="max-w-4xl mx-auto p-6">
    <!-- Stats Dashboard -->
    <div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-6">
      <div class="bg-white rounded-lg shadow p-4">
        <p class="text-gray-500 text-sm">Alle Tasks</p>
        <p class="text-3xl font-bold">{{ tasks.length }}</p>
      </div>
      
      <div class="bg-blue-50 rounded-lg shadow p-4">
        <p class="text-blue-600 text-sm">Offen</p>
        <p class="text-3xl font-bold text-blue-600">{{ openTasksCount }}</p>
      </div>
      
      <div class="bg-green-50 rounded-lg shadow p-4">
        <p class="text-green-600 text-sm">Erledigt</p>
        <p class="text-3xl font-bold text-green-600">{{ completedTasksCount }}</p>
      </div>
      
      <div class="bg-purple-50 rounded-lg shadow p-4">
        <p class="text-purple-600 text-sm">Fortschritt</p>
        <p class="text-3xl font-bold text-purple-600">{{ completionPercentage }}%</p>
      </div>
    </div>
    
    <!-- Search & Filter -->
    <div class="bg-white rounded-lg shadow p-4 mb-6">
      <div class="flex flex-col md:flex-row gap-4">
        <!-- Search -->
        <div class="flex-1">
          <input 
            v-model="search"
            type="text"
            placeholder="Tasks durchsuchen..."
            class="w-full border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
          >
          <p v-if="search" class="text-sm text-gray-500 mt-1">
            {{ filteredAndSearchedTasks.length }} von {{ tasks.length }} Tasks gefunden
          </p>
        </div>
        
        <!-- Filter Buttons -->
        <div class="flex gap-2">
          <button 
            @click="filter = 'all'"
            :class="[
              'px-4 py-2 rounded-lg font-medium transition-colors',
              filter === 'all' 
                ? 'bg-blue-500 text-white' 
                : 'bg-gray-200 text-gray-700 hover:bg-gray-300'
            ]"
          >
            Alle
          </button>
          <button 
            @click="filter = 'open'"
            :class="[
              'px-4 py-2 rounded-lg font-medium transition-colors',
              filter === 'open' 
                ? 'bg-blue-500 text-white' 
                : 'bg-gray-200 text-gray-700 hover:bg-gray-300'
            ]"
          >
            Offen
          </button>
          <button 
            @click="filter = 'completed'"
            :class="[
              'px-4 py-2 rounded-lg font-medium transition-colors',
              filter === 'completed' 
                ? 'bg-blue-500 text-white' 
                : 'bg-gray-200 text-gray-700 hover:bg-gray-300'
            ]"
          >
            Erledigt
          </button>
        </div>
        
        <!-- Sort -->
        <select 
          v-model="sortBy"
          class="border border-gray-300 rounded-lg px-4 py-2 focus:ring-2 focus:ring-blue-500"
        >
          <option value="title">Nach Titel</option>
          <option value="date-desc">Neueste zuerst</option>
          <option value="date-asc">Älteste zuerst</option>
        </select>
      </div>
    </div>
    
    <!-- Task List -->
    <div class="space-y-3">
      <div 
        v-for="task in sortedTasks" 
        :key="task.id"
        class="bg-white rounded-lg shadow-sm hover:shadow-md transition-shadow p-4 flex items-center gap-4"
      >
        <!-- Checkbox -->
        <input 
          type="checkbox"
          :checked="task.completed"
          @change="toggleTask(task.id)"
          class="w-5 h-5 text-blue-600 rounded focus:ring-2 focus:ring-blue-500"
        >
        
        <!-- Content -->
        <div class="flex-1">
          <h3 
            :class="[
              'font-semibold',
              task.completed ? 'line-through text-gray-500' : 'text-gray-800'
            ]"
          >
            {{ task.title }}
          </h3>
          <p class="text-sm text-gray-600">
            {{ formatDate(task.createdAt) }}
          </p>
        </div>
        
        <!-- Delete Button -->
        <button 
          @click="deleteTask(task.id)"
          class="text-red-500 hover:text-red-700 transition-colors"
        >
          🗑️
        </button>
      </div>
      
      <!-- Empty State -->
      <div 
        v-if="sortedTasks.length === 0"
        class="text-center py-12 text-gray-500"
      >
        <p class="text-xl mb-2">🔍</p>
        <p>Keine Tasks gefunden</p>
        <p class="text-sm" v-if="search || filter !== 'all'">
          Versuche einen anderen Filter oder Suchbegriff
        </p>
      </div>
    </div>
    
    <!-- Success Message -->
    <div 
      v-if="allTasksCompleted && tasks.length > 0"
      class="mt-6 bg-green-50 border border-green-200 rounded-lg p-4 text-center"
    >
      <p class="text-green-800 text-lg font-semibold">
        🎉 Alle Tasks erledigt! Großartige Arbeit!
      </p>
    </div>
  </div>
</template>

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

// Reactive Data
const tasks = ref([
  { id: 1, title: 'Vue.js Reaktivität lernen', completed: false, createdAt: '2025-11-01T10:00:00' },
  { id: 2, title: 'Kat für Tutorial danken', completed: false, createdAt: '2025-11-01T11:00:00' },
  { id: 3, title: 'Task-Manager deployen', completed: false, createdAt: '2025-11-01T12:00:00' },
]);

const search = ref('');
const filter = ref('all'); // 'all', 'open', 'completed'
const sortBy = ref('title'); // 'title', 'date-desc', 'date-asc'

// Computed Properties - Stats
const openTasksCount = computed(() => {
  return tasks.value.filter(t => !t.completed).length;
});

const completedTasksCount = computed(() => {
  return tasks.value.filter(t => t.completed).length;
});

const completionPercentage = computed(() => {
  if (tasks.value.length === 0) return 0;
  return Math.round((completedTasksCount.value / tasks.value.length) * 100);
});

const allTasksCompleted = computed(() => {
  return tasks.value.length > 0 && tasks.value.every(t => t.completed);
});

// Computed Properties - Filtering & Sorting
const filteredTasks = computed(() => {
  if (filter.value === 'all') return tasks.value;
  if (filter.value === 'open') return tasks.value.filter(t => !t.completed);
  if (filter.value === 'completed') return tasks.value.filter(t => t.completed);
  return tasks.value;
});

const filteredAndSearchedTasks = computed(() => {
  if (!search.value) return filteredTasks.value;
  
  const searchLower = search.value.toLowerCase();
  return filteredTasks.value.filter(t => 
    t.title.toLowerCase().includes(searchLower)
  );
});

const sortedTasks = computed(() => {
  const tasksCopy = [...filteredAndSearchedTasks.value];
  
  if (sortBy.value === 'title') {
    return tasksCopy.sort((a, b) => a.title.localeCompare(b.title));
  }
  
  if (sortBy.value === 'date-desc') {
    return tasksCopy.sort((a, b) => 
      new Date(b.createdAt) - new Date(a.createdAt)
    );
  }
  
  if (sortBy.value === 'date-asc') {
    return tasksCopy.sort((a, b) => 
      new Date(a.createdAt) - new Date(b.createdAt)
    );
  }
  
  return tasksCopy;
});

// Watchers - Side Effects
watch(search, (newSearch) => {
  // Save to localStorage
  localStorage.setItem('taskSearch', newSearch);
  
  // Analytics (optional)
  if (newSearch) {
    console.log('User searched for:', newSearch);
  }
});

watch(filter, (newFilter) => {
  localStorage.setItem('taskFilter', newFilter);
});

watch(allTasksCompleted, (completed) => {
  if (completed) {
    console.log('🎉 All tasks completed!');
    // Könnte hier eine Konfetti-Animation triggern!
  }
});

// Methods - Actions
function toggleTask(id) {
  const task = tasks.value.find(t => t.id === id);
  if (task) {
    task.completed = !task.completed;
  }
}

function deleteTask(id) {
  if (confirm('Task wirklich löschen?')) {
    tasks.value = tasks.value.filter(t => t.id !== id);
  }
}

function formatDate(dateString) {
  const date = new Date(dateString);
  return date.toLocaleDateString('de-DE', {
    day: '2-digit',
    month: '2-digit',
    year: 'numeric'
  });
}

// Load from localStorage on mount
const savedSearch = localStorage.getItem('taskSearch');
if (savedSearch) {
  search.value = savedSearch;
}

const savedFilter = localStorage.getItem('taskFilter');
if (savedFilter) {
  filter.value = savedFilter;
}
</script>

Nova: „WOW! Das ist… ein kompletter Task-Manager mit Stats, Filter, Search, Sort… alles reaktiv!“ 🤩

Kat: „Und alles updated sich automatisch! Ändere einen Wert – BOOM, alle berechneten Werte updaten sich! Das ist die Macht von Reaktivität!“


🎯 Java-Vergleich: Reaktivität in Vue vs Java

Kat erklärt für Backend-Devs:

Java Observer Pattern

// Java - Manuell Observer implementieren
public class TaskList extends Observable {
    private List<Task> tasks = new ArrayList<>();
    
    public void addTask(Task task) {
        tasks.add(task);
        setChanged();
        notifyObservers(); // ← Manuell benachrichtigen!
    }
}

public class TaskView implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        // ← Manuell UI updaten!
        updateUI();
    }
}

Vue Reaktivität

<script setup>
// Vue - Automatisch!
const tasks = ref([]);

function addTask(task) {
  tasks.value.push(task);
  // ← Vue updated UI automatisch! 🪄
}
</script>

Unterschied:

  • Java: Manuelles Observer-Pattern implementieren
  • Vue: Observer-Pattern eingebaut, automatisch!

Nova: „Ah! Vue macht das Observer-Pattern für mich!“

Kat: „Genau! Du musst nur ref() oder reactive() nutzen – den Rest macht Vue!“


✅ Deine Erfolgs-Checkliste für heute

Nach diesem Teil kannst du:

  • [ ] Reaktivität verstehen (wie Vue Änderungen trackt!)
  • [ ] ref() nutzen (für Primitives & Arrays)
  • [ ] reactive() verstehen (für Objects)
  • [ ] .value richtig verwenden (im Script, nicht im Template!)
  • [ ] Arrays manipulieren (push, splice, filter, map)
  • [ ] Computed Properties erstellen (abgeleitete Werte!)
  • [ ] Watchers nutzen (auf Änderungen reagieren)
  • [ ] Unterschied Computed vs Methods kennen
  • [ ] Reaktivitäts-Fallen vermeiden
  • [ ] Production-Ready Task-Manager bauen

Wenn alle ✅ sind: Du verstehst Vue’s Magie! Das ist HUGE! 🎉


🚀 Nächste Woche: CRUD Complete – Edit & Delete!

Kat’s Vorschau:

„Jetzt wo du Reaktivität verstehst, wird’s einfacher!

📝 Teil 5: CRUD Operations Complete

Was du lernen wirst:

  • Tasks bearbeiten (Edit-Modus!)
  • Tasks löschen (mit Confirm!)
  • Optimistic UI Updates
  • Error Handling beim Edit
  • Inline-Editing vs Modal
  • UX Best Practices

Nova’s Challenge: ‚Wie wechsle ich zwischen View und Edit-Mode?‘ Kat’s Solution: ‚Conditional Rendering mit v-if!‘

Real talk: CRUD ist der Kern jeder App. Danach kannst du JEDE Todo/Task/Note-App bauen!

Bis nächste Woche! 💚“


❓ FAQ – Reaktivität Edition

Frage 1: Warum brauche ich .value bei ref() aber nicht bei reactive()?

Antwort: ref() wrapped den Wert in ein Object mit .value Property. Das ist nötig damit Vue primitives (numbers, strings) reaktiv machen kann. reactive() macht Objects direkt reaktiv, ohne Wrapper. Im Template unwrapped Vue automatisch, deshalb brauchst du dort kein .value!

Frage 2: Kann ich ref() und reactive() mischen?

Antwort: Ja, kein Problem! Du kannst in einer Component beide nutzen:

const count = ref(0); // ref
const user = reactive({ name: 'Nova' }); // reactive

Aber: Pick one Style und bleib dabei für Konsistenz! Empfehlung: ref() für alles, ist einfacher!

Frage 3: Wann ist Computed besser als Watch?

Antwort: Computed für abgeleitete Werte die du im Template anzeigen willst. Watch für Side-Effects (API-Calls, LocalStorage, Analytics). Regel: Wenn du was RETURN willst → Computed. Wenn du was TUN willst → Watch!

Frage 4: Verliere ich Reaktivität wenn ich ein Array filter()?

Antwort: Nein! filter(), map(), reduce() geben ein NEUES Array zurück – das ist OK und reaktiv! Vue erkennt das. Problem ist nur wenn du Properties außerhalb von ref/reactive änderst.

Frage 5: Muss ich immer .value schreiben? Das nervt!

Antwort: Im <script> ja, im <template> nein! Vue unwrapped automatisch im Template. Tipp: Nach ein paar Tagen wird’s Muskelgedächtnis – du schreibst .value automatisch! 😄

Frage 6: Was passiert wenn ich .value vergesse?

Antwort: Du überschreibst das ref-Object statt den Wert zu ändern! Beispiel:

const count = ref(0);
count = 5; // ❌ Überschreibt ref-Object!
count.value = 5; // ✅ Ändert reaktiven Wert

Erste Version bricht Reaktivität – UI updated nicht mehr!

Frage 7: Was macht ihr bei komplexen Reaktivitäts-Problemen?

Antwort: Honestly? Debuggen! Vue DevTools sind dein Freund – du siehst live welche Werte reaktiv sind und wann sie sich ändern. Aber real talk: Manchmal sind die komplexesten Bugs nicht im Code, sondern in unserem Kopf. Wenn du zu lange an einem Problem sitzt, mach eine Pause. Geh spazieren. Bei mir sind die besten Lösungen beim Gassi mit Pixel gekommen. 🐕 Manchmal braucht unser Brain einfach… Abstand. Das gilt für Code UND für Leben. Wer mehr über die „Mental Breaks“ zwischen den Coding-Sessions lesen will, findet vielleicht was in unseren „behind the code“ Logs. Lowkey therapeutic, ngl. 💭

Frage 8: Können Computed Properties andere Computed Properties nutzen?

Antwort: Ja, absolut! Computed Properties können auf andere Computed Properties zugreifen:

const openTasks = computed(() => tasks.value.filter(t => !t.completed));
const openTasksTitles = computed(() => openTasks.value.map(t => t.title));

Vue trackt automatisch alle Dependencies!

Frage 9: Ist Reaktivität performant bei großen Listen?

Antwort: Ja! Vue’s Reaktivitäts-System ist sehr optimiert. Auch mit 1000+ Items kein Problem. Wenn du wirklich Performance-Probleme hast (>10.000 Items), gibt’s Strategien: Virtual Scrolling, Pagination, shallowRef(). Aber für 99% der Apps: Vue ist fast genug!

Frage 10: Was ist besser: ref() oder reactive()?

Antwort: Geschmackssache! Beide haben Vor-/Nachteile:

  • ref(): Sicherer (kann reassigned werden), nervig (.value), funktioniert mit allem
  • reactive(): Cleaner (kein .value), unsicherer (kein reassign), nur für Objects

Empfehlung für Anfänger: Bleib bei ref() – es ist flexibler und du machst weniger Fehler!


📖 Vue.js für Anfänger – Alle Teile im Überblick

✅ Bereits veröffentlicht:

  • Teil 1 Setup & Erste API-Anbindung
  • Teil 2 : Styling & Responsive Design
  • Teil 3 : Formulare & User Input
  • Teil 4 : Listen-Manipulation & Reaktivität – Du bist hier! ✨

🔜 Kommende Teile:

  • Teil 5 : CRUD Operations Complete (Edit & Delete)
  • Teil 6 : Component Communication (Props & Emits!)
  • Teil 7 : State Management (Composables & Pinia)
  • Teil 8 : Routing & Multi-Page Apps
  • Teil 9 : API Integration & Error Handling
  • Teil 10 : Testing mit Vitest
  • Teil 11 : Performance & Production-Ready
  • Teil 12 : Nova’s eigenes Projekt „Book Buddy“ 🎓

Alle Teile der Serie findest du hier: Link zur Serie-Übersicht


🔧 Tools & Extensions für Reaktivität

Für diese Session MUST-HAVE:

ToolZweckWarum wichtig?
Vue DevToolsLive Reaktivität beobachten!Sieh in Echtzeit welche Werte sich ändern
VolarVS Code Vue-SupportAuto-Complete für ref(), computed(), watch()
ConsoleDebuggingconsole.log() ist dein Freund!

Vue DevTools für Reaktivität:

# So nutzt du Vue DevTools für Reaktivität
1. F12 → Vue Tab öffnen
2. Component auswählen
3. "State" Tab → Sieh alle ref() und reactive() Werte
4. "Timeline" Tab → Sieh WANN sich Werte ändern!
5. Klick auf einen Wert → Ändere ihn live!

Kat’s Pro-Tipp: „Im Timeline-Tab siehst du die Reihenfolge: User-Event → State-Änderung → Re-Render. Das hilft ENORM beim Debuggen von Reaktivitäts-Problemen!“

Debugging-Workflow:

// Temporär einbauen zum Debuggen
watch(tasks, (newVal, oldVal) => {
  console.log('Tasks changed!', { old: oldVal, new: newVal });
}, { deep: true });

💬 Real Talk: Reaktivitäts-Magie

Java Fleet Küche, 13:00 Uhr. Nova sitzt mit leuchtenden Augen vor ihrem Laptop, Kat holt sich einen Kaffee. Cassian kommt von einem Meeting und schnappt sich einen Apfel.


Nova: „Cassian! Du kommst genau richtig – ich hab gerade Reaktivität verstanden!“

Cassian: [setzt sich] „Das klingt nach einem Aha-Moment. Erzähl.“

Nova: „Also, Vue trackt automatisch welche Daten sich ändern und updated die UI! Das ist wie… magic!“

Kat: [schmunzelt] „Magic mit System. Vue nutzt Proxies unter der Haube.“

Cassian: „Wie Observer-Pattern in Java, nur eleganter. Kein manuelles notifyObservers() mehr.“

Nova: „GENAU! Das hab ich mir auch gedacht! Aber ich hatte ein Problem: Ich hab tasks.push() gemacht statt tasks.value.push() – und nichts hat sich geupdated!“

Kat: „Classic .value vergessen. Passiert jedem.“

Cassian: „Bei mir war’s ref() vs reactive(). Ich hab reactive() für ein Array genommen und konnte es nicht neu zuweisen. Zwei Stunden debugging.“

Nova: „Oh nein! Was ist die Lösung?“

Kat: „ref() für alles was du neu zuweisen willst. reactive() nur für Objects deren Properties du änderst, nie das Object selbst.“

Cassian: „Oder einfach immer ref() nehmen. Simpler mental model.“

Nova: „Das sagt Kat auch! Aber was ist mit computed()? Das ist auch magic!“

Kat: „Computed ist cached magic. Berechnet nur neu wenn Dependencies sich ändern. Perfekt für abgeleitete Werte.“

Cassian: „Performance-technisch ein Game-Changer. Stell dir vor, du hast eine teure Berechnung die sich bei jedem Render wiederholt – mit computed sparst du dir das.“

Nova: „Und watch()?“

Kat: „Watch ist für Side-Effects. Wenn sich X ändert, mach Y. Perfekt für API-Calls, LocalStorage, Analytics.“

Cassian: „Aber aufpassen mit deep watching. Das kann Performance-Probleme machen bei großen Objects.“

Nova: [tippt eifrig] „Ich schreib mir das alles auf!“

[Kurze Pause. Cassian schaut auf sein Handy.]

Cassian: „Reaktivität ist übrigens nicht nur ein Code-Konzept. Das Leben ist auch reaktiv. Wir ändern uns, unsere Umgebung reagiert…“

Kat: [schaut kurz auf] „Tiefgründig für einen Dienstag-Mittag.“

Cassian: „ML-Modelle haben mich philosophisch gemacht. Alles ist Input → Processing → Output. Auch Beziehungen.“ [grinst]

Nova: „Okay, DAS ist zu deep für mich. Ich bleib bei Vue-Reaktivität!“

Kat: [lacht] „Smart. Aber honestly? Cassian hat nicht unrecht. Manchmal wünschte ich, ich könnte mein Leben auch mit computed() optimieren. ‚Current Happiness: based on Work, Pixel, Sleep…'“

Cassian: „Klingt nach einem Side-Project.“

Kat: „Klingt nach etwas für die… anderen Logs.“ [trinkt ihren Kaffee]

Nova: „Mysterious! Aber jetzt erstmal: Mein Task-Manager braucht noch Filter-Buttons!“


🎁 Bonus: Vue Reactivity Cheat Sheet

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

// ===== REACTIVE DATA =====

// ref() - Primitives & Arrays
const count = ref(0);
const name = ref('Nova');
const tasks = ref([]);

// reactive() - Objects
const user = reactive({
  name: 'Nova',
  age: 24
});

// ===== ACCESSING VALUES =====

// In <script>:
console.log(count.value); // Mit .value!
console.log(user.name);   // Ohne .value!

// In <template>:
{{ count }}      // Ohne .value!
{{ user.name }}  // Direkt!

// ===== UPDATING VALUES =====

// ref()
count.value = 5;
tasks.value.push(newTask);
tasks.value = [...newTasks]; // Reassign OK!

// reactive()
user.name = 'Kat';
user = {}; // ❌ FEHLER! Kein reassign!

// ===== COMPUTED =====

const doubled = computed(() => {
  return count.value * 2;
});

const filteredTasks = computed(() => {
  return tasks.value.filter(t => !t.completed);
});

// ===== WATCH =====

// Single source
watch(count, (newVal, oldVal) => {
  console.log(`Changed from ${oldVal} to ${newVal}`);
});

// Multiple sources
watch([count, name], ([newCount, newName]) => {
  console.log('Count or name changed!');
});

// With options
watch(count, callback, {
  immediate: true,  // Run on mount
  deep: true       // Deep watch objects
});

// ===== HELPERS =====

// toRefs() - Destructure reactive
const { name, age } = toRefs(user);
name.value = 'Kat'; // Updates user.name!

// unref() - Get plain value
import { unref } from 'vue';
const plainValue = unref(count); // Gibt 0 zurück (nicht ref)

</script>

🎨 Challenge für die Community!

Kat’s Herausforderung:

„Du verstehst jetzt Reaktivität! Zeit für eine Challenge:

Level 1 – Computed Practice:

  • 📊 Stats Dashboard (Total, Open, Completed)
  • 🔍 Search + Filter kombinieren
  • 📈 Fortschritts-Percentage

Level 2 – Advanced Filtering:

  • 🏷️ Multi-Tag-Filter (mehrere Tags gleichzeitig)
  • 📅 Date-Range-Filter (von-bis)
  • ⭐ Priority-Filter (High, Medium, Low)

Level 3 – Watchers & LocalStorage:

  • 💾 Auto-Save bei Änderungen (watch)
  • 🔄 Restore from LocalStorage beim Mount
  • 📊 Analytics tracken (welche Filter werden genutzt?)

Teile dein Ergebnis:

  • Screenshot posten mit #VueReactivityChallenge
  • GitHub-Repo verlinken
  • Erkläre welche computed Properties du erstellt hast

Die cleversten Lösungen featuren wir! 🎉“


💬 Das war Teil 4 der Vue.js für Anfänger Serie!

Nova & Kat: „Von Confusion zur Clarity! 🤯➡️💡“

Du hast heute das HERZSTÜCK von Vue.js verstanden! Reaktivität ist was Vue so mächtig macht!

Was du geschafft hast:

✅ Reaktivität verstanden (Observer-Pattern!) ✅ ref() vs reactive() kennengelernt ✅ .value richtig verwendet ✅ Arrays manipuliert (push, splice, filter) ✅ Computed Properties erstellt ✅ Watchers genutzt ✅ Reaktivitäts-Fallen vermieden ✅ Production-Ready Task-Manager mit Stats gebaut

Real talk: Das war der technisch tiefste Teil bisher. Wenn du das verstanden hast, bist du kein Anfänger mehr – du bist ein „Advanced Beginner“! 💪 Reaktivität zu verstehen ist wie die Matrix zu sehen – plötzlich macht ALLES Sinn!

Hast du es nachgebaut? Teile deinen Task-Manager!

Fragen? Schreib uns:

  • Nova: nova.trent@java-developer.online
  • Kat: katharina.schmidt@java-developer.online

Nächster Teil: In 1 Woche! CRUD Complete – Edit & Delete mit Stil! 🚀

Keep coding, keep learning! 💡

Nova & Kat – Von der Magie zur Meisterung, gemeinsam! 💚


Tags: #VueJS #Reaktivität #Computed #Watch #ref #reactive #Frontend #WebDevelopment #StateManagement #JavaScript


© 2025 Java Fleet Systems Consulting | java-developer.online

Autoren

  • Ensign Nova Trent

    24 Jahre alt, frisch von der Universität als Junior Entwicklerin bei Java Fleet Systems Consulting. Nova ist brilliant in Algorithmen und Datenstrukturen, aber neu in der praktischen Java-Enterprise-Entwicklung. Sie brennt darauf, ihre ersten echten Projekte zu bauen und entdeckt dabei die Lücke zwischen Uni-Theorie und Entwickler-Realität. Sie liebt Star Treck das ist der Grund warum alle Sie Ensign Nova nennen und arbeitet daraufhin das sie Ihren ersten Knopf am Kragen bekommt.

  • Katharina Schmidt

    Katharina „Kat“ Schmidt – Die Design-Denkerin

    Senior Frontend- & UI/UX-Developerin | 29 Jahre | „Design ist Kommunikation – nicht Dekoration.“

    Kat ist das Gesicht hinter dem, was Nutzer sehen, fühlen und anklicken.
    In einem Team, das tief in Backend-Architekturen, Security und Data Flows denkt, sorgt sie dafür, dass der Mensch nicht vergessen wird.
    Ihre Arbeitsweise ist wie ihr Stil: präzise, ruhig, ästhetisch – und immer auf das Wesentliche reduziert.

    Sie kam 2022 zu Java Fleet, mit einem Hintergrund in Medieninformatik und einer klaren Mission: gute Technik sichtbar machen.
    Sie übersetzt komplexe Backend-Prozesse in klare Interfaces, macht aus endlosen Formularen strukturierte Nutzerreisen – und schafft es, dass ein Button „richtig“ wirkt, ohne dass jemand erklären kann, warum.

    💻 Die Tech-Seite

    Kat beherrscht den kompletten modernen Frontend-Stack:
    React, Vue.js, Tailwind CSS, SASS, Figma, Storybook – sie baut Systeme, keine Einzelkomponenten.
    Für sie ist Design kein Add-on, sondern ein integraler Teil der Architektur.
    Wenn sie an einer Oberfläche arbeitet, denkt sie wie ein Entwickler; wenn sie mit Entwicklern spricht, denkt sie wie eine Nutzerin.

    „Design ohne Verständnis für Code ist Deko. Code ohne Verständnis für Menschen ist Selbstgespräch.“

    Kat liebt es, Barrieren zu entfernen – technische, visuelle und emotionale.
    Accessibility (WCAG 2.1), Responsiveness, Semantik – das sind für sie keine Checklisten, sondern Selbstverständlichkeiten.

    🌿 Die menschliche Seite

    Kat ist der kreative Ruhepol im Team.
    Sie arbeitet konzentriert, lacht leise, und wenn sie eine Idee erklärt, spürt man sofort: Sie hat sie nicht nur verstanden – sie hat sie durchdacht.
    In ihrem Büro stehen eine Zimmerpflanze, ein Notizblock mit Skizzen, ein Grafiktablett und eine große Tasse Tee.
    Wenn sie nachdenkt, zeichnet sie. Wenn sie zuhört, beobachtet sie.

    Sie ist die empathischste Entwicklerin im Raum – und genau das macht sie so wichtig.
    Nova nennt sie „meine UI-Großschwester“, Cassian bewundert ihre gedankliche Klarheit, und Franz-Martin sagt:

    „Kat sieht das, was wir übersehen – und sie sagt’s, ohne laut zu werden.“

    🧠 Ihre Rolle im Team

    Kat ist die Schnittstelle zwischen Technik und Mensch – zwischen Backend und Benutzer, Code und Kommunikation.
    Sie organisiert Design-System-Workshops, dokumentiert Prinzipien, und sorgt dafür, dass jedes Projekt der Java Fleet ein Gesicht bekommt.
    Dabei denkt sie nicht in Farben oder Fonts, sondern in Abläufen und Emotionen: Wie fühlt sich ein Login an? Wann entsteht Vertrauen?
    Ihr Ziel: Interfaces, die sich richtig anfühlen – weil sie logisch sind.

    ⚡ Superkraft

    Empathie in Codeform.
    Kat übersetzt komplexe Technik in nutzerfreundliche Realität – elegant, barrierefrei und ehrlich.

    💬 Motto

    „Design ist, wenn du nicht erklären musst, wie es funktioniert.“