Watchers
Reaktive Zustandsänderungen beobachten und darauf reagieren
Was sind Watchers?
In Vue hast du mit computed bereits gelernt, wie du abgeleitete Werte aus reaktivem State berechnest. Aber was, wenn du auf eine Änderung reagieren willst — zum Beispiel einen API-Call auslösen, einen Timer starten oder einen Log-Eintrag schreiben? Genau dafür gibt es Watchers.
Ein Watcher beobachtet eine oder mehrere reaktive Quellen und führt eine Callback-Funktion aus, sobald sich der Wert ändert. Anders als computed gibt ein Watcher keinen Wert zurück — er führt einen Seiteneffekt aus.
Faustregel
computed = abgeleiteter Wert, watch = Seiteneffekt. Wenn du einen Wert berechnen willst, nimm computed. Wenn du auf eine Änderung reagieren willst (API-Call, DOM-Manipulation, Logging), nimm watch. watch() für Refs
Die einfachste Form von watch() beobachtet eine einzelne Ref. Der Callback erhält zwei Argumente: den neuen und den alten Wert. So kannst du genau nachvollziehen, was sich geändert hat.
<script setup>
import { ref, watch } from 'vue'
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log(`Counter: ${oldValue} → ${newValue}`)
if (newValue >= 10) {
alert('Du hast 10 erreicht!')
}
})
</script>
<template>
<button @click="counter++">
Zähler: {{ counter }}
</button>
</template> Der Watcher wird erst ausgelöst, wenn sich counter tatsächlich ändert — nicht beim initialen Rendern. Das ist ein wichtiger Unterschied zu watchEffect(), den wir gleich kennenlernen.
watch() für reactive Objekte
Wenn du ein mit reactive() erstelltes Objekt beobachtest, ist der Watcher automatisch deep — er reagiert auf jede Änderung in verschachtelten Properties. Willst du hingegen nur eine einzelne Property beobachten, musst du eine Getter-Funktion verwenden.
<script setup>
import { reactive, watch } from 'vue'
const user = reactive({
name: 'Max',
email: 'max@example.com',
address: {
city: 'Berlin'
}
})
// Ganzes Objekt beobachten — automatisch deep
watch(user, (newUser) => {
console.log('User geändert:', newUser)
})
// Einzelne Property — Getter-Funktion nötig!
watch(
() => user.name,
(newName, oldName) => {
console.log(`Name: ${oldName} → ${newName}`)
}
)
// Verschachtelte Property — ebenfalls Getter
watch(
() => user.address.city,
(newCity) => {
console.log('Neue Stadt:', newCity)
}
)
</script>Getter-Funktion bei reactive Properties
watch(user.name, callback) funktioniert nicht — das übergibt nur den aktuellen String-Wert, keine reaktive Referenz. Verwende stattdessen immer eine Getter-Funktion: watch(() => user.name, callback). Mehrere Quellen beobachten
Du kannst auch mehrere Quellen gleichzeitig beobachten, indem du ein Array übergibst. Der Callback erhält dann Arrays mit den neuen und alten Werten — in derselben Reihenfolge wie die Quellen.
<script setup>
import { ref, watch } from 'vue'
const firstName = ref('Max')
const lastName = ref('Mustermann')
watch(
[firstName, lastName],
([newFirst, newLast], [oldFirst, oldLast]) => {
console.log(`Name geändert: ${oldFirst} ${oldLast} → ${newFirst} ${newLast}`)
// z.B. API-Call für Profil-Update
updateProfile(newFirst, newLast)
}
)
</script>Das ist besonders nützlich, wenn eine Aktion von mehreren Werten abhängt — zum Beispiel ein API-Call, der sowohl Filter als auch Seitenzahl berücksichtigen muss.
watchEffect()
watchEffect() ist die bequemere Variante: Du musst nicht angeben, was du beobachten willst — Vue erkennt die Abhängigkeiten automatisch anhand der reaktiven Werte, die du innerhalb des Callbacks verwendest. Außerdem wird der Callback sofort beim Erstellen einmal ausgeführt.
<script setup>
import { ref, watchEffect } from 'vue'
const searchQuery = ref('')
const category = ref('alle')
const page = ref(1)
// Vue erkennt automatisch: searchQuery, category und page
// werden gelesen → bei Änderung eines der drei wird
// der Callback erneut ausgeführt
watchEffect(() => {
console.log(
`Suche: "${searchQuery.value}" in ${category.value}, Seite ${page.value}`
)
// API-Call mit allen drei Parametern
fetchResults(searchQuery.value, category.value, page.value)
})
// ↑ Wird sofort beim Erstellen einmal ausgeführt!
</script>watch vs watchEffect — Wann was?
Verwende watch(), wenn du den alten Wert brauchst, nur auf bestimmte Quellen reagieren willst oder den Callback nicht sofort ausführen möchtest. Verwende watchEffect(), wenn du viele Abhängigkeiten hast und der Code dadurch übersichtlicher wird.
Wann ist watchEffect die bessere Wahl?
watchEffect() oft übersichtlicher als watch([a, b, c], ...). Du sparst dir die explizite Auflistung und die Destrukturierung im Callback. Optionen: immediate und deep
watch() akzeptiert ein drittes Argument mit Optionen. Die zwei wichtigsten:
immediate: true— Der Callback wird sofort beim Erstellen einmal ausgeführt, nicht erst bei der ersten Änderung. Praktisch, wenn du zum Beispiel bei Seitenaufruf direkt Daten laden willst.deep: true— Bei Refs, die Objekte enthalten, wird standardmäßig nur die Referenz beobachtet. Mitdeep: truereagiert der Watcher auch auf Änderungen in verschachtelten Properties.
<script setup>
import { ref, watch } from 'vue'
const userId = ref(42)
const settings = ref({ theme: 'dark', lang: 'de' })
// immediate: true — lädt Daten sofort beim Mount
watch(userId, async (newId) => {
const response = await fetch(`/api/users/${newId}`)
const data = await response.json()
console.log('User geladen:', data)
}, { immediate: true })
// deep: true — reagiert auf verschachtelte Änderungen
watch(settings, (newSettings) => {
console.log('Einstellungen geändert:', newSettings)
localStorage.setItem('settings', JSON.stringify(newSettings))
}, { deep: true })
</script>Watchers stoppen
Watchers, die in setup() oder <script setup> erstellt werden, sind automatisch an die Komponenten-Instanz gebunden und werden beim Unmount gestoppt. Manchmal willst du einen Watcher aber vorher manuell stoppen — zum Beispiel wenn eine Bedingung erfüllt ist.
<script setup>
import { ref, watch } from 'vue'
const source = ref(0)
const dataLoaded = ref(false)
// watch() gibt eine Stop-Funktion zurück
const stopWatcher = watch(source, async (newVal) => {
const data = await fetchData(newVal)
if (data.complete) {
dataLoaded.value = true
// Watcher manuell stoppen — wird nicht mehr ausgelöst
stopWatcher()
console.log('Watcher gestoppt, Daten vollständig geladen.')
}
})
</script>watch() und watchEffect() geben beide eine Stop-Funktion zurück. Ein Aufruf dieser Funktion entfernt den Watcher sofort.
watch vs computed — Entscheidungshilfe
Die Abgrenzung zwischen computed und watch ist eine der häufigsten Fragen für Vue-Einsteiger. Hier eine klare Entscheidungshilfe:
Verwende computed, wenn…
- du einen Wert aus anderen Werten ableiten willst
- das Ergebnis im Template angezeigt wird
- keine Seiteneffekte nötig sind
Beispiel: const fullName = computed(() => firstName.value + ' ' + lastName.value)
Verwende watch, wenn…
- du einen Seiteneffekt auslösen willst (API-Call, Timer, Logging)
- du den alten Wert brauchst
- du eine asynchrone Operation starten willst
- du auf Änderungen bedingt reagieren willst
Beispiel: Bei Änderung der Suchbegriffe einen API-Call auslösen.
Die goldene Regel
const x = computed(() => ...) formulieren? Dann nimm computed. Brauchst du fetch(), console.log() oder setTimeout() im Callback? Dann nimm watch. Rails-Vergleich
Als Rails-Entwickler kennst du ActiveRecord Callbacks wie before_save, after_create und after_update. Diese reagieren auf Zustandsänderungen deiner Modelle — genau wie Vue-Watchers auf reaktive Zustandsänderungen reagieren.
ActiveRecord Callbacks vs Vue Watchers
Gemeinsamkeit: Beide reagieren auf Zustandsänderungen und führen Seiteneffekte aus. Rails-Callbacks reagieren auf Persistenz-Events (save, create, update), Vue-Watchers auf reaktive Datenänderungen im Browser.
Unterschied: Rails-Callbacks sind serverseitig und oft asynchron (Datenbankoperationen). Vue-Watchers sind clientseitig und laufen synchron im Reactive-System — außer du startest darin explizit asynchrone Operationen.
Dirty Tracking: Rails bietet mit ActiveModel::Dirty Methoden wie changed? und name_was. In Vue erhältst du dasselbe über die (newValue, oldValue)-Parameter im Watcher-Callback.
Rails-Callback
class User < ApplicationRecord
# ActiveRecord Callbacks — reagieren auf Persistenz-Events
after_update :notify_name_change
private
def notify_name_change
# ActiveModel::Dirty — Zugriff auf alte Werte
if saved_change_to_name?
old_name = name_before_last_save
new_name = name
UserMailer.name_changed(self, old_name, new_name).deliver_later
Rails.logger.info "Name geändert: #{old_name} → #{new_name}"
end
end
endVue-Watcher — dasselbe Konzept im Browser
<script setup>
import { ref, watch } from 'vue'
const userName = ref('Max Mustermann')
// Vue Watcher — reagiert auf reaktive Änderungen
watch(userName, (newName, oldName) => {
// Wie saved_change_to_name? + name_before_last_save
console.log(`Name geändert: ${oldName} → ${newName}`)
// Seiteneffekt: API-Call (wie deliver_later in Rails)
fetch('/api/profile', {
method: 'PATCH',
body: JSON.stringify({ name: newName })
})
})
</script>
<template>
<input v-model="userName" />
</template>Der entscheidende Unterschied: In Rails läuft der Callback beim Speichern in die Datenbank. In Vue läuft er sofort, wenn sich der reaktive Wert ändert — noch bevor irgendein API-Call gemacht wird.
Interaktive Demo
Probiere Watchers selbst aus! Diese Demo zeigt einen typischen Anwendungsfall: Eine Suche mit Debounce. Der Watcher reagiert auf Änderungen im Suchfeld, wartet kurz ab (Debounce) und filtert dann die Ergebnisse.