watch und Suchfunktionalität

In diesem Kapitel lernst du, watch und watchEffect zu verwenden, um auf Datenänderungen zu hören und eine Echtzeitfilterung im Blog-Suchfeld zu implementieren.


Welche Szenarien benötigen watch?

In den meisten Fällen ist Vues reaktive Bindung ausreichend: Wenn sich Daten ändern, aktualisiert sich die Vorlage automatisch.

Aber einige Szenarien erfordern, dass duproaktiv ein Stück Logik ausführst, wenn sich Daten ändern, wie zum Beispiel:

  • Nach der Eingabe in das Suchfeld die Artikelliste filtern
  • Wenn der Benutzer die Kategorien wechselt, den Seitentitel aktualisieren
  • Nach Datenänderungen in localStorage speichern
  • Wenn sich Formularfelder ändern, Formularvalidierung durchführen

Dann brauchst duwatchundwatchEffect。


watch — präzise Überwachung

watchHöre auf angegebene Datenquellen und führe eine Callback-Funktion aus, wenn sich Daten ändern.

Du kannst denneuen Wertund denalten Wertabrufen, um sie zu vergleichen.

Beispiel

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

const keyword = ref('')

// watch(die überwachte Datenquelle, Callback-Funktion)
watch(keyword, (newVal, oldVal) => {
console.log(`Der Suchbegriff hat sich von "${oldVal}" zu "${newVal}" geändert`)
// Filterlogik hier ausführen
})
</script>

<template>
  <input v-model="keyword" placeholder="Artikel suchen...">
  <p>Aktuelle Suche: {{ keyword }}</p>
</template>

Drei Datenquellen für watch

DatenquellentypSyntaxBeispiel
Einzelne refwatch(ref, callback)watch(keyword, fn)
Eigenschaft eines reaktiven Objektswatch(() => obj.prop, callback)watch(() => user.name, fn)
Mehrere Datenquellenwatch([ref1, ref2], callback)watch([kw, cat], fn)

Beispiel

import { ref, watch } from 'vue'

const keyword = ref('')
const category = ref('All')

// Eine einzelne ref überwachen
watch(keyword, (newVal) => {
  document.title = newVal ?`Search: ${newVal}` : 'EXAMPLE Blog'
})

// Mehrere Datenquellen überwachen
watch([keyword, category], ([newKw, newCat], [oldKw, oldCat]) => {
  console.log('Keyword oder Kategorie geändert, Artikel werden neu gefiltert')
  // Filterlogik ausführen...
})

Standardmäßig wird watchverzögert ausgeführt—es wird nicht ausgelöst, wenn Daten zum ersten Mal einen Wert haben, sondern nur bei nachfolgenden Änderungen. Wenn du es einmal während der Initialisierung ausführen möchtest, füge die{ immediate: true }Konfigurationsoption hinzu.


watchEffect — Verfolgt automatisch Abhängigkeiten

watchEffectDatenquellen müssen nicht manuell angegeben werden; es verfolgt automatisch die im Callback verwendeten reaktiven Daten.

Der größte Unterschied zu watch:watchEffect wird sofort einmal ausgeführt。

Beispiel

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

const keyword = ref('')
const articles = ref([
{ id: 1, title: 'Vue3-Einführung' },
{ id: 2, title: 'JS Async' },
])

const filteredArticles = ref([])

// watchEffect verfolgt keyword und articles automatisch
// Und es wird sofort einmal ausgeführt, um filteredArticles zu initialisieren
watchEffect(() => {
// Alle in der Funktion verwendeten reaktiven Daten werden automatisch erfasst
  filteredArticles.value = articles.value.filter(
    a => a.title.includes(keyword.value)
  )
console.log(`Filterergebnis: ${filteredArticles.value.length} Artikel`)
})
</script>

<template>
  <input v-model="keyword" placeholder="Suchen">
  <div v-for="a in filteredArticles" :key="a.id">
    {{ a.title }}
  </div>
</template>

watch vs watchEffect Vergleich

MerkmalwatchwatchEffect
Ob die Datenquelle angegeben istManuell angegebenVerfolgt Abhängigkeiten automatisch
Erste AusführungWird standardmäßig nicht ausgeführt (konfigurierbar mit immediate)Wird sofort ausgeführt
Abrufen des alten WertsKann den alten Wert abrufenKann den alten Wert nicht abrufen
Anwendbare SzenarienWenn Sie alte und neue Werte vergleichen und den Zeitpunkt präzise steuern müssenWenn Ihnen alte/neue Werte egal sind und Sie nur auf Änderungen reagieren möchten

Einfache Auswahlhilfe: Wenn Ihre Logik lautet: „Wenn sich X ändert, tue Y“, verwenden Sie watch. Wenn Ihre Logik lautet: „Sobald sich irgendeine Abhängigkeit ändert, wird das Ergebnis neu berechnet“, verwenden Sie watchEffect oder computed. In der Praxis sollte bei Daten, die berechnet werden können, zuerst computed verwendet werden; watch dient der Behandlung von „Seiteneffekten“ – wie dem Aufrufen von APIs, dem lokalen Speichern oder der Manipulation des DOM.


Debounce-Ansatz

Jeder Tastendruck im Suchfeld löst eine Filterung aus. Für lokale Daten ist das in Ordnung, aber wenn jede Eingabe eine Netzwerkanfrage sendet, wird viel verschwendet.

DebounceDie Idee: Warten, bis der Benutzer eine Zeit lang nicht mehr tippt, und dann die Operation ausführen.

Beispiel

import { ref, watch } from 'vue'

const keyword = ref('')
const filteredArticles = ref([])

let timer = null   // Timer-ID

watch(keyword, (newVal) => {
  // Den vorherigen Timer löschen
  clearTimeout(timer)
  // Filterung nur ausführen, wenn innerhalb von 300ms keine neue Eingabeänderung erfolgt
  timer = setTimeout(() => {
    filteredArticles.value = articles.value.filter(
      a => a.title.includes(newVal) || a.summary.includes(newVal)
    )
  }, 300)
})

Für dieses Blogprojekt sind die Daten lokal, daher ist Debouncing nicht notwendig. Aber dieses Konzept zu verstehen, ist später für größere Projekte sehr hilfreich.


Praxis: Echtzeitsuche zum Blog hinzufügen

Füge eine Suchbox zur NavBar-Komponente hinzu, um Artikeltitel und Zusammenfassungen in Echtzeit nach Stichwort zu filtern.

Beispiel

<!-- Dateipfad: src/views/HomeView.vue -->
<script setup>
import { ref, computed, onMounted } from 'vue'
import BlogCard from '../components/BlogCard.vue'
import CategoryFilter from '../components/CategoryFilter.vue'

const articles = ref([]) // Alle Artikel
const isLoading = ref(true)
const activeCategory = ref('All')
const keyword = ref('') // Suchbegriff

// Alle Kategorien extrahieren
const categories = computed(() => {
  const cats = articles.value.map(a => a.category)
return ['Alle', ...new Set(cats)]
})

// computed verfolgt automatisch Änderungen an keyword und activeCategory
// Wenn sich eines davon ändert, wird das Filterergebnis automatisch neu berechnet
const filteredArticles = computed(() => {
  let result = articles.value

// Schritt 1: Nach Kategorie filtern
if (activeCategory.value !== 'All') {
    result = result.filter(a => a.category === activeCategory.value)
  }

// Schritt 2: Nach Suchbegriff filtern (Titel und Zusammenfassung durchsuchen)
  if (keyword.value.trim()) {
    const kw = keyword.value.trim().toLowerCase()
    result = result.filter(a =>
      a.title.toLowerCase().includes(kw) ||
      a.summary.toLowerCase().includes(kw)
    )
  }

  return result
})

// Das Wechseln von Kategorien löscht die Suche nicht; die beiden funktionieren in Kombination
function handleCategoryChange(cat) {
  activeCategory.value = cat
}

// Daten laden
onMounted(async () => {
  try {
    const res = await fetch('/posts.json')
    articles.value = await res.json()
  } catch (err) {
console.error('Laden fehlgeschlagen', err)
  } finally {
    isLoading.value = false
  }
})
</script>

<template>
  <div class="home">
    <!-- Suchfeld -->
    <div class="search-bar">
      <input
       v-model="keyword"
       type="text"
       placeholder="Titel oder Zusammenfassung des Artikels suchen..."
       class="search-input"
     />
      <span v-if="keyword" class="clear-btn" @click="keyword = ''">✕</span>
    </div>

    <!-- Kategoriefilter -->
    <CategoryFilter
     :="categories"
     active-category="activeCategory"
     @update-category="handleCategoryChange"
   />

    <!-- Laden / Leer / Normal -->
    <p v-if="isLoading">Wird geladen...</p>
    <p v-else-if="filteredArticles.length === 0">Keine passenden Artikel gefunden</p>
    <div v-else class="article-grid">
      <BlogCard
       v-for="article in filteredArticles"
       :key="article.id"
       :id="article.id"
       :title="article.title"
       :summary="article.summary"
       :date="article.date"
       :category="article.category"
     />
    </div>
  </div>
</template>

<style scoped>
.home { max-width: 960px; margin: 0 auto; padding: 20px; }

.search-bar {
  position: relative;
  margin-bottom: 20px;
}

.search-input {
  width: 100%;
  padding: 12px 40px 12px 16px;
  border: 2px solid #eee;
  border-radius: 8px;
  font-size: 15px;
  outline: none;
  transition: border-color 0.2s;
}

.search-input:focus {
  border-color: #427DB8;
}

.clear-btn {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  cursor: pointer;
  color: #999;
  font-size: 18px;
}

.article-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 24px;
  margin-top: 20px;
}
</style>

Jetzt arbeiten Suche und Kategoriefilterung zusammen – nach Eingabe eines Suchbegriffs werden beim Klicken auf eine Kategorie-Schaltfläche die Filter gestapelt. Beide Bedingungen sind reaktiv; wenn sich eine von beiden ändert, wird filteredArticles automatisch aktualisiert.


Kapitelzusammenfassung

In diesem Kapitel hast du watch gemeistert (Datenquelle manuell angeben, alte und neue Werte abrufen) sowie watchEffect (verfolgt Abhängigkeiten automatisch, wird sofort ausgeführt) und gelernt, computed für die kombinierte Suche + Kategoriefilterung zu verwenden.

Das Echtzeit-Filtererlebnis eines Suchfelds ist viel flüssiger als "tippen und auf eine Schaltfläche klicken"—das ist der Wert eines reaktiven Systems.

Andere Erweiterungen