Skip to content

Kapitel 23: Composition API (Teil 3) - computed() & watch()

📙 Lernziel: computed() und watch() in Composition API meistern!


23.1 computed() in Composition API

computed() in Composition API ist einfacher als Options API.

Syntax:

javascript
import { computed } from 'vue'

const berechneteEigenschaft = computed(() => {
  // Logik
  return ergebnis
})

Beispiel:

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

const items = ref([
  { id: 1, text: 'Lernen', done: false },
  { id: 2, text: 'Sport', done: true },
  { id: 3, text: 'Einkaufen', done: false }
])

// computed() - Anzahl erledigt
const doneCount = computed(() => {
  return items.value.filter(item => item.done).length
})

// computed() - Anzahl offen
const undoneCount = computed(() => {
  return items.value.filter(item => !item.done).length
})

// computed() - Alle erledigt?
const allDone = computed(() => {
  return items.value.length > 0 && doneCount.value === items.value.length
})
</script>

<template>
  <div>
    <h2>To-Do Liste</h2>
    <p>Gesamt: {{ items.length }}</p>
    <p>Erledigt: {{ doneCount }}</p>
    <p>Offen: {{ undoneCount }}</p>
    <p>Alle erledigt? {{ allDone ? 'Ja!' : 'Nein' }}</p>
    
    <ul>
      <li v-for="item in items" :key="item.id">
        <input type="checkbox" v-model="item.done" />
        <span :style="{ textDecoration: item.done ? 'line-through' : 'none' }">
          {{ item.text }}
        </span>
      </li>
    </ul>
  </div>
</template>

Wichtig:

  • computed() gibt ein readonly ref zurück!
  • ✅ Nur im <script> mit .value zugreifen
  • ✅ Im <template> direkt nutzen (ohne .value)

23.2 computed() mit Setter

computed() mit Setter (selten nötig):

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

const firstName = ref('Max')
const lastName = ref('Mustermann')

// computed() mit Getter und Setter
const fullName = computed({
  // Getter
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // Setter
  set(newValue) {
    const names = newValue.split(' ')
    firstName.value = names[0]
    lastName.value = names[names.length - 1]
  }
})
</script>

<template>
  <div>
    <p>Vorname: <input v-model="firstName" /></p>
    <p>Nachname: <input v-model="lastName" /></p>
    <p>Vollständiger Name: <input v-model="fullName" /></p>
  </div>
</template>

23.3 watch() in Composition API

watch() in Composition API ist flexibler.

Syntax:

javascript
import { watch } from 'vue'

watch(quelle, callback, optionen)

Beispiel (einzelne Quelle):

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

const count = ref(0)
const message = ref('Hallo')

// watch() - Überwacht count
watch(count, (newValue, oldValue) => {
  console.log(`count geändert: ${oldValue} → ${newValue}`)
})

// watch() - Überwacht message
watch(message, (newValue, oldValue) => {
  console.log(`message geändert: ${oldValue} → ${newValue}`)
})
</script>

<template>
  <div>
    <p>Zähler: {{ count }}</p>
    <button @click="count++">Erhöhen</button>
    
    <p>Nachricht: {{ message }}</p>
    <button @click="message = 'Vue 3 ist toll!'">Nachricht ändern</button>
  </div>
</template>

23.4 watch() für mehrere Quellen

Mehrere Quellen gleichzeitig überwachen:

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

const firstName = ref('')
const lastName = ref('')

// Array von Quellen
watch([firstName, lastName], (newValues, oldValues) => {
  console.log('Vorname oder Nachname geändert!')
  console.log('Neu:', newValues)
  console.log('Alt:', oldValues)
})
</script>

<template>
  <div>
    <label>Vorname: <input v-model="firstName" /></label><br>
    <label>Nachname: <input v-model="lastName" /></label><br>
    <p>Vollständiger Name: {{ firstName }} {{ lastName }}</p>
  </div>
</template>

23.5 watch() für Objekte (Tiefenüberwachung)

Problem: watch() überwacht standardmäßig nur Referenzänderungen.

Lösung: deep: true Option

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

const user = reactive({
  name: 'Max',
  address: {
    city: 'Berlin',
    street: 'Hauptstraße'
  }
})

// ❌ Funktioniert nicht für tiefe Änderungen
watch(user, () => {
  console.log('user geändert')
})

// ✅ Mit deep: true
watch(user, () => {
  console.log('user (tief) geändert')
}, { deep: true })

// ✅ Bessere Lösung: Nur spezifisches Property überwachen
watch(() => user.address.city, (newCity) => {
  console.log('Stadt geändert:', newCity)
})
</script>

<template>
  <div>
    <p>Name: {{ user.name }}</p>
    <p>Stadt: {{ user.address.city }}</p>
    <button @click="user.address.city = 'München'">Stadt ändern</button>
  </div>
</template>

Empfehlung:

  • Nutze () => user.address.city statt deep: true (effizienter)!

23.6 watchEffect() in Composition API

watchEffect() führt eine Funktion sofort aus und überwacht alle darin verwendeten reaktiven Daten.

Beispiel:

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

const count = ref(0)
const message = ref('Hallo')

// watchEffect() - Führt sofort aus und überwacht Abhängigkeiten
watchEffect(() => {
  console.log(`count: ${count.value}, message: ${message.value}`)
})

// count erhöhen
const increment = () => {
  count.value++
}

// message ändern
const changeMessage = () => {
  message.value = 'Vue 3 ist toll!'
}
</script>

<template>
  <div>
    <p>Zähler: {{ count }}</p>
    <button @click="increment">Erhöhen</button>
    
    <p>Nachricht: {{ message }}</p>
    <button @click="changeMessage">Nachricht ändern</button>
  </div>
</template>

23.7 watch() vs watchEffect() - Wann was nutzen?

watch() nutzen wenn:

  • ✅ Du brauchst oldValue und newValue
  • ✅ Du willst die Quelle explizit angeben
  • ✅ Du willst nicht sofort ausführen (nur bei Änderung)

watchEffect() nutzen wenn:

  • ✅ Du willst sofort ausführen
  • ✅ Du hast mehrere Abhängigkeiten (automatische Erkennung)
  • ✅ Du brauchst keine oldValue

Beispiel - API Aufruf:

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

const userId = ref(1)
const userData = ref(null)

// watch() - API Aufruf nur wenn userId sich ändert
watch(userId, async (newId) => {
  const response = await fetch(`https://jsonplaceholder.typicode.com/users/${newId}`)
  userData.value = await response.json()
}, { immediate: true }) // Sofort ausführen
</script>

<template>
  <div>
    <button @click="userId++">Nächster User</button>
    <div v-if="userData">
      <h2>{{ userData.name }}</h2>
      <p>Email: {{ userData.email }}</p>
    </div>
  </div>
</template>

23.8 Übung: Suchfeld mit watch() (Debouncing)

Aufgabe: Erstelle ein Suchfeld, das bei Eingabe eine API abfragt (Debouncing).

Lösung:

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

const searchQuery = ref('')
const results = ref([])
const isLoading = ref(false)
let timeoutId = null

// watch() mit Debouncing
watch(searchQuery, (newQuery) => {
  if (newQuery.trim() === '') {
    results.value = []
    return
  }
  
  // Debouncing: Warte 500ms vor API Aufruf
  clearTimeout(timeoutId)
  timeoutId = setTimeout(async () => {
    isLoading.value = true
    try {
      const response = await fetch(`https://api.example.com/search?q=${newQuery}`)
      results.value = await response.json()
    } catch (error) {
      console.error('Fehler:', error)
    } finally {
      isLoading.value = false
    }
  }, 500)
})
</script>

<template>
  <div>
    <input 
      v-model="searchQuery" 
      placeholder="Suchen..."
    />
    <div v-if="isLoading">Lädt...</div>
    <ul v-else>
      <li v-for="result in results" :key="result.id">
        {{ result.name }}
      </li>
    </ul>
  </div>
</template>

✅ Zusammenfassung

In diesem Kapitel hast du gelernt:

  • computed() in Composition API nutzen
  • computed() mit Setter
  • watch() in Composition API nutzen
  • watch() für mehrere Quellen
  • watch() für Objekte (deep: true)
  • watchEffect() in Composition API nutzen
  • ✅ Unterschied zwischen watch() und watchEffect()
  • ✅ Praxis: Suchfeld mit Debouncing

🎯 Nächster Schritt: In Kapitel 24 lernst du Lebenszyklus-Hooks in Composition API!


← Zurück zu Kapitel 22: Reaktive GrundlagenWeiter zu Kapitel 24: Lebenszyklus-Hooks →

Frei für alle Anfänger