Skip to content

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 Options-API können wir die Option watch verwenden, um eine Funktion auszulösen, sobald sich eine reaktive Eigenschaft ändert:

js
export default {
  data() {
    return {
      question: '',
      answer: 'Questions usually contain a question mark. ;-)'
    }
  },
  watch: {
    // whenever question changes, this function will run
    question(newQuestion, oldQuestion) {
      if (newQuestion.includes('?')) {
        this.getAnswer()
      }
    }
  },
  methods: {
    async getAnswer() {
      this.answer = 'Thinking...'
      try {
        const res = await fetch('https://yesno.wtf/api')
        this.answer = (await res.json()).answer
      } catch (error) {
        this.answer = 'Error! Could not reach the API. ' + error
      }
    }
  }
}
template
<p>
  Stellen Sie eine Ja/Nein-Frage:
  <input v-model="question" />
</p>
<p>{{ answer }}</p>

Versuchen Sie es auf dem Spielplatz

Die Option watch unterstützt auch einen durch Punkte getrennten Pfad als Schlüssel:

js
export default {
  watch: {
    // Hinweis: nur einfache Pfade. Ausdrücke werden nicht unterstützt.
    'some.nested.key'(newValue) {
      // ...
    }
  }
}

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

watch ist standardmäßig oberflächlich: der Callback wird nur ausgelöst, wenn der überwachten Eigenschaft ein neuer Wert zugewiesen wurde - er wird nicht bei verschachtelten Eigenschaftsänderungen ausgelöst. Wenn Sie möchten, dass der Callback bei allen verschachtelten Änderungen ausgelöst wird, müssen Sie einen Deep Watcher verwenden:

js
export default {
  watch: {
    someObject: {
      handler(newValue, oldValue) {
        // Hinweis: `newValue` ist hier gleich `oldValue`
        // bei verschachtelten Mutationen, solange das Objekt selbst
        // nicht ersetzt worden ist.
      },
      deep: true
    }
  }
}

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.

Eager Watchers

watch is lazy by default: the callback is only called when the monitored source has changed. But in some cases, we want the same callback logic to run eagerly - for example, we want to retrieve some initial data and then retrieve the data again when the relevant state changes.

Wir können erzwingen, dass der Rückruf eines Watchers sofort ausgeführt wird, indem wir ihn als Objekt mit einer handler-Funktion und der Option immediate: true deklarieren:

js
export default {
  // ...
  watch: {
    question: {
      handler(newQuestion) {
        // wird dies sofort bei der Erstellung der Komponente ausgeführt.
      },
      // eifrige Callback-Ausführung erzwingen
      immediate: true
    }
  }
  // ...
}

Die anfängliche Ausführung der Handler-Funktion wird kurz vor dem created-Hook erfolgen. Vue hat bereits die Optionen data, computed und methods verarbeitet, so dass diese Eigenschaften beim ersten Aufruf verfügbar sind.

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 auch watchEffect 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
export default {
  // ...
  watch: {
    key: {
      handler() {},
      flush: 'post'
    }
  }
}
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 */
})

this.$watch()

Es ist auch möglich, zwingend Watcher zu erstellen, indem man die $watch() instance method:

js
export default {
  created() {
    this.$watch('question', (newQuestion) => {
      // ...
    })
  }
}

Dies ist nützlich, wenn Sie einen Watcher bedingt einrichten oder etwas nur als Reaktion auf eine Benutzerinteraktion beobachten wollen. Außerdem können Sie damit den Watcher frühzeitig beenden.

Stopping a Watcher

Watcher, die mit der Option watch oder der Instanzmethode $watch() deklariert wurden, werden automatisch gestoppt, wenn die Eigentümerkomponente ausgehängt wird, so dass Sie sich in den meisten Fällen nicht selbst um das Stoppen des Watchers kümmern müssen.

In dem seltenen Fall, dass Sie einen Watcher stoppen müssen, bevor die Owner-Komponente aussteigt, gibt die $watch() API eine Funktion dafür zurück:

js
const unwatch = this.$watch('foo', callback)

// ...wenn der Beobachter nicht mehr benötigt wird:
unwatch()

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
  }
})
Watchers has loaded