Watchers (Obserwatorzy)
Jobs by vuejobs.com
Przykład podstawowy
Właściwości obliczeniowe pozwalają nam deklaratywnie obliczać wartości pochodne. Istnieją jednak przypadki, w których musimy wykonać " działanie poboczne" w reakcji na zmiany stanu - na przykład zmutować DOM lub zmienić inny element stanu w oparciu o wynik operacji asynchronicznej.
W Composition API, możemy użyć funkcji watch
, aby wywołać wywołanie zwrotne za każdym razem, gdy zmieni się jakiś element stanu reaktywnego:
vue
<script setup>
import { ref, watch } from 'vue'
const question = ref('')
const answer = ref('Questions usually contain a question mark. ;-)')
// obserwacja działa bezpośrednio na 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>
Typy źróddeł obserwatorów
Opcja watch
obsługuje również ścieżkę w postaci kropek jako klucz: // Uwaga: tylko proste typy ścieżek. Wyrażenia nie są obsługiwane. Pierwszym argumentem watch
mogą być różne typy reaktywnych "źródeł": może to być ref (włączając w to obliczone refy), obiekt reaktywny, funkcja pobierająca lub tablica wielu źródeł:
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 of multiple sources
watch([x, () => y.value], ([newX, newY]) => {
console.log(`x is ${newX} and y is ${newY}`)
})
Należy pamiętać, że nie można w ten sposób obserwować właściwości obiektu reaktywnego:
js
const obj = reactive({ count: 0 })
// this won't work because we are passing a number to watch()
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})
Zamiast tego,używaj getter:
js
// zamiast tego,używaj getter:
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
Głeboki Watcher
Kiedy wywołasz watch()
bezpośrednio na obiekcie reaktywnym, utworzy on niejawnie głębokiego obserwatora - wywołanie zwrotne zostanie wywołane na wszystkich zagnieżdżonych mutacjach:
js
const obj = reactive({ count: 0 })
watch(obj, (newValue, oldValue) => {
// wywołuje mutacje właściwości zagnieżdżonych
// Uwaga: `newValue` będzie tutaj równe `oldValue`.
// ponieważ obie wskazują na ten sam obiekt!
})
obj.count++
Należy to odróżnić od gettera, który zwraca obiekt reaktywny - w tym drugim przypadku wywołanie zwrotne zostanie wywołane tylko wtedy, gdy getter zwróci inny obiekt:
js
watch(
() => state.someObject,
() => {
// wywołuje się tylko wtedy, gdy state.someObject zostanie zastąpiony
}
)
Można jednak zmusić drugi przypadek do głębokiej obserwacji, jawnie używając opcji deep
:
js
watch(
() => state.someObject,
(newValue, oldValue) => {
// Uwaga: `newValue` będzie tutaj równe `oldValue`.
// *chyba że* state.someObject został zastąpiony
},
{ deep: true }
)
Używaj z rozwagą
Głęboka obserwacja wymaga prześledzenia wszystkich zagnieżdżonych właściwości obserwowanego obiektu i może być kosztowna, gdy jest używana na dużych strukturach danych. Używaj go tylko wtedy, gdy jest to konieczne i uważaj na jego wpływ na wydajność.
watchEffect()
Funkcja watch()
jest leniwa: wywołanie zwrotne nie zostanie wywołane, dopóki obserwowane źródło się nie zmieni. Jednak w niektórych przypadkach możemy chcieć, aby ta sama logika wywołania zwrotnego była uruchamiana natychmiast - na przykład, możemy chcieć pobrać pewne dane początkowe, a następnie ponownie pobrać te dane, gdy tylko zmieni się odpowiedni stan. Może się zdarzyć, że tak właśnie zrobimy:
js
const url = ref('https://...')
const data = ref(null)
async function fetchData() {
const response = await fetch(url.value)
data.value = await response.json()
}
// pobieraj natychmiast
fetchData()
// ...a następnie obserwuj zmianę adresu url
watch(url, fetchData)
Można to uprościć za pomocą [watchEffect()
] (/api/reactivity-core.html#watcheffect). Funkcja watchEffect()
pozwala nam na natychmiastowe wykonanie efektu ubocznego, jednocześnie automatycznie śledząc reaktywne zależności tego efektu. Powyższy przykład może być przepisany jako:
js
watchEffect(async () => {
const response = await fetch(url.value)
data.value = await response.json()
})
W tym przypadku wywołanie zwrotne zostanie uruchomione natychmiast. Podczas jego wykonywania, będzie on również automatycznie śledził url.value
jako zależność (podobnie jak w przypadku właściwości computed). Kiedykolwiek url.value
się zmieni, callback zostanie uruchomiony ponownie.
Możesz sprawdzić ten przykład z watchEffect
i reaktywnym pobieraniem danych w akcji.
TIP
Funkcja watchEffect
śledzi zależności tylko podczas swojego synchronicznego wykonania. W przypadku użycia go z wywołaniem zwrotnym asynchronicznym, śledzone będą tylko właściwości dostępne przed pierwszym await
.
watch
vs. watchEffect
Zarówno watch
jak i watchEffect
pozwalają nam na reaktywne wykonywanie efektów ubocznych. Ich główną różnicą jest sposób w jaki śledzą swoje reaktywne zależności:
watch
śledzi tylko jawnie obserwowane źródło. Nie będzie śledzić niczego, do czego uzyskuje się dostęp wewnątrz wywołania zwrotnego. Dodatkowo, wywołanie zwrotne jest wyzwalane tylko wtedy, gdy źródło rzeczywiście się zmieniło.watch
oddziela śledzenie zależności od efektu ubocznego, dając nam bardziej precyzyjną kontrolę nad tym, kiedy wywołanie zwrotne powinno zostać odpalone.Z drugiej strony,
watchEffect
łączy śledzenie zależności i efekt uboczny w jedną fazę. Automatycznie śledzi każdą właściwość reaktywną, do której uzyskano dostęp podczas jej synchronicznego wykonywania. Jest to wygodniejsze i zazwyczaj powoduje, że kod jest krótszy, ale powoduje, że zależności reaktywne są mniej wyraźne.
Callback Flush Timing
Kiedy mutujesz stan reaktywny, może on wywoływać zarówno aktualizacje komponentów Vue, jak i wywołania zwrotne obserwatorów utworzone przez użytkownika.
Domyślnie, wywołania zwrotne obserwatora utworzone przez użytkownika są wywoływane przed aktualizacjami komponentów Vue. Oznacza to, że jeśli użytkownik spróbuje uzyskać dostęp do DOM wewnątrz wywołania zwrotnego obserwatora, DOM będzie znajdował się w stanie, w którym Vue nie zastosowało żadnych aktualizacji.
Jeśli chcesz uzyskać dostęp do DOM w wywołaniu zwrotnym obserwatora po aktualizacji przez Vue, musisz określić opcję flush: 'post'
:
js
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
Post-flush watchEffect()
ma także wygodny alias, watchPostEffect()
:
js
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* executed after Vue updates */
})
Zatrzymywanie Watcher
Watchery zadeklarowane synchronicznie wewnątrz setup()
lub <script setup>
są związane z instancją komponentu właściciela i zostaną automatycznie zatrzymane, gdy ten zostanie odmontowany. W większości przypadków nie musisz się martwić o zatrzymywanie watchera.
Kluczem jest to, że watcher musi być tworzony synchronicznie: jeśli watcher jest tworzony w wywołaniu zwrotnym asynchronicznym, nie będzie powiązany z komponentem właściciela i musi być zatrzymany ręcznie, aby uniknąć wycieków pamięci. Oto przykład:
vue
<script setup>
import { watchEffect } from 'vue'
// ten zostanie automatycznie zatrzymany
watchEffect(() => {})
// ...ten nie będzie!
setTimeout(() => {
watchEffect(() => {})
}, 100)
</script>
Aby ręcznie zatrzymać watchera, użyj funkcji return handle. Działa to zarówno dla watch
jak i watchEffect
:
js
const unwatch = watchEffect(() => {})
// ...później, gdy nie będzie już potrzebny
unwatch()
Należy pamiętać, że powinno być bardzo mało przypadków, w których trzeba tworzyć watchery asynchronicznie, a tworzenie synchroniczne powinno być preferowane zawsze, gdy jest to możliwe. Jeśli musisz czekać na dane asynchroniczne, możesz zamiast tego uczynić logikę obserwacyjną warunkową:
js
// dane ładowane asynchronicznie
const data = ref(null)
watchEffect(() => {
if (data.value) {
// zrób coś, gdy dane zostaną załadowane
}
})