Watchers
Grundlegendes Beispiel
Mit berechneten Eigenschaften können wir abgeleitete Werte deklarativ berechnen. Es gibt jedoch Fälle, in denen als Reaktion auf Zustandsänderungen "Seiteneffekte" ausgeführt werden müssen, z. B. Mutation des DOM oder Änderung eines anderen Teils des Zustands auf der Grundlage des Ergebnisses einer asynchronen Operation.
Mit der Composition API können wir die Funktion watch
verwenden, um einen Callback auszulösen, sobald sich ein Teil des reaktiven Zustands ändert:
vue
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('Fragen enthalten in der Regel ein Fragezeichen. ;-)')
// Uhr arbeitet direkt auf einem Ref
watch(question, async (newQuestion, oldQuestion) => {
if (newQuestion.indexOf('?') > -1) {
answer.value = 'Thinking...'
try {
const res = await fetch('https://yesno.wtf/api')
answer.value = (await res.json()).answer
} catch (error) {
answer.value = 'Error! Could not reach the API. ' + error
}
}
})
</script>
<template>
<p>
Ask a yes/no question:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
</template>
Versuchen Sie es auf dem Spielplatz
Quellenarten beobachten
Das erste Argument von watch
kann aus verschiedenen Arten von reaktiven "Quellen" bestehen: Es kann ein ref (einschließlich berechneter refs), ein reaktives Objekt, eine Getter-Funktion oder ein Array aus mehreren Quellen sein:
js
const x = ref(0)
const y = ref(0)
// single ref
watch(x, (newX) => {
console.log(`x is ${newX}`)
})
// getter
watch(
() => x.value + y.value,
(sum) => {
console.log(`sum of x + y is: ${sum}`)
}
)
// Array aus mehreren Quellen
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
Beachten Sie, dass Sie eine Eigenschaft eines reaktiven Objekts nicht auf diese Weise überwachen können:
js
const obj = reactive({ count: 0 })
// dies wird nicht funktionieren, da wir eine Zahl an watch() übergeben
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})
Verwenden Sie stattdessen einen Getter:
js
// verwenden Sie stattdessen einen Getter:
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
Deep Watchers
Wenn Sie watch()
direkt auf einem reaktiven Objekt aufrufen, wird implizit ein Deep Watcher erstellt - der Callback wird bei allen verschachtelten Mutationen ausgelöst:
js
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// feuert auf verschachtelte Eigenschaftsmutationen
// Hinweis: `newValue` wird hier gleich `oldValue` sein
// weil sie beide auf dasselbe Objekt zeigen!
})
obj.count++
Dies ist zu unterscheiden von einem Getter, der ein reaktives Objekt zurückgibt - im letzteren Fall wird der Callback nur ausgelöst, wenn der Getter ein anderes Objekt zurückgibt:
js
watch(
() => state.someObject,
() => {
// wird nur ausgelöst, wenn state.someObject ersetzt wird
}
)
Sie können jedoch den zweiten Fall in einen Deep Watcher erzwingen, indem Sie explizit die Option deep
verwenden:
js
watch(
() => state.someObject,
(newValue, oldValue) => {
// Hinweis: `newValue` ist hier gleich `oldValue`
// *außer* state.someObject wurde ersetzt
},
{ deep: true }
)
Verwendung mit Vorsicht
Deep Watch erfordert das Durchlaufen aller verschachtelten Eigenschaften des überwachten Objekts und kann bei großen Datenstrukturen teuer sein. Verwenden Sie es nur, wenn es notwendig ist, und achten Sie auf die Auswirkungen auf die Leistung.
watchEffect()
watch()
ist träge: der Callback wird erst aufgerufen, wenn sich die überwachte Quelle geändert hat. Aber in einigen Fällen möchten wir vielleicht, dass die gleiche Callback-Logik eifrig ausgeführt wird - zum Beispiel möchten wir einige anfängliche Daten abrufen und dann die Daten erneut abrufen, wenn sich der relevante Zustand ändert. Möglicherweise werden wir dies tun:
js
const url = ref('https://...')
const data = ref(null)
async function fetchData() {
const response = await fetch(url.value)
data.value = await response.json()
}
// sofort abrufen
fetchData()
// ...dann auf Url-Änderung achten
watch(url, fetchData)
Dies kann mit watchEffect()
vereinfacht werden. Mit watchEffect()
können wir einen Seiteneffekt sofort ausführen und dabei automatisch die reaktiven Abhängigkeiten des Effekts verfolgen. Das obige Beispiel kann umgeschrieben werden als:
js
watchEffect(async () => {
const response = await fetch(url.value)
data.value = await response.json()
})
Hier wird der Callback sofort ausgeführt. Während seiner Ausführung wird er auch automatisch url.value
als Abhängigkeit verfolgen (ähnlich wie bei berechneten Eigenschaften). Wann immer sich url.value
ändert, wird der Callback erneut ausgeführt.
Sie können sich dieses Beispiel mit watchEffect
und reaktivem Datenabruf in Aktion ansehen.
TIP
watchEffect
verfolgt Abhängigkeiten nur während seiner synchronen Ausführung. Wenn es mit einem asynchronen Callback verwendet wird, werden nur Eigenschaften verfolgt, auf die vor dem ersten await
-Tick zugegriffen wird.
watch
vs. watchEffect
Sowohl watch
als auch watchEffect
erlauben es uns, reaktiv Seiteneffekte auszuführen. Ihr Hauptunterschied ist die Art und Weise, wie sie ihre reaktiven Abhängigkeiten verfolgen:
Sowohl
watch
als auchwatchEffect
erlauben es uns, reaktiv Seiteneffekte auszuführen. Ihr Hauptunterschied ist die Art und Weise, wie sie ihre reaktiven Abhängigkeiten verfolgen.watchEffect
hingegen kombiniert die Verfolgung von Abhängigkeiten und Seiteneffekten in einer Phase. Sie verfolgt automatisch jede reaktive Eigenschaft, auf die während ihrer synchronen Ausführung zugegriffen wird. Dies ist bequemer und führt in der Regel zu kürzerem Code, macht aber die reaktiven Abhängigkeiten weniger explizit.
Callback Flush Timing
Wenn Sie den reaktiven Zustand verändern, kann dies sowohl Aktualisierungen der Vue-Komponenten als auch von Ihnen erstellte Watcher-Rückrufe auslösen.
Standardmäßig werden vom Benutzer erstellte Watcher-Callbacks vor den Aktualisierungen der Vue-Komponenten aufgerufen. Das bedeutet, wenn Sie versuchen, auf das DOM innerhalb eines Watcher-Callbacks zuzugreifen, wird das DOM in dem Zustand sein, bevor Vue irgendwelche Updates angewendet hat.
Wenn Sie in einem Watcher-Callback auf das DOM zugreifen wollen, ** nachdem** Vue es aktualisiert hat, müssen Sie die Option flush: 'post'
angeben:
js
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
Post-flush watchEffect()
hat auch einen praktischen Alias, watchPostEffect()
:
js
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* executed after Vue updates */
})
Stopping a Watcher
Watcher, die synchron innerhalb von setup()
oder <script setup>
deklariert werden, sind an die Instanz der Eigentümerkomponente gebunden und werden automatisch gestoppt, wenn die Eigentümerkomponente ausgehängt wird. In den meisten Fällen müssen Sie sich nicht darum kümmern, den Watcher selbst zu stoppen.
Der Schlüssel hierzu ist, dass der Watcher synchron erstellt werden muss: Wenn der Watcher in einem asynchronen Callback erstellt wird, wird er nicht an die Eigentümerkomponente gebunden und muss manuell gestoppt werden, um Speicherlecks zu vermeiden. Hier ist ein Beispiel:
vue
<script setup>
import { watchEffect } from 'vue'
// diese wird automatisch gestoppt
watchEffect(() => {})
// ...dieser wird es nicht!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
Um einen Watcher manuell zu stoppen, verwenden Sie die zurückgegebene Handle-Funktion. Dies funktioniert sowohl für watch
als auch für watchEffect
:
js
const unwatch = watchEffect(() => {})
// ...später, wenn sie nicht mehr benötigt werden
unwatch()
Beachten Sie, dass es nur sehr wenige Fälle geben sollte, in denen Sie Watcher asynchron erstellen müssen, und dass die synchrone Erstellung wann immer möglich bevorzugt werden sollte. Wenn Sie auf einige asynchrone Daten warten müssen, können Sie Ihre Watch-Logik stattdessen an Bedingungen knüpfen:
js
// asynchron zu ladende Daten
const data = ref(null)
watchEffect(() => {
if (data.value) {
// etwas tun, wenn Daten geladen sind
}
})