watch et fonctionnalité de recherche
Dans ce chapitre, vous apprendrez à utiliser watch et watchEffect pour écouter les changements de données et implémenter un filtrage en temps réel dans la boîte de recherche du blog.
Quels scénarios nécessitent watch ?
Dans la plupart des cas, la liaison réactive de Vue est suffisante : lorsque les données changent, le template se met à jour automatiquement.
Mais certains scénarios nécessitent que vousexécutiez proactivement une logique lorsque les données changent, tels que :
- Après avoir saisi dans la boîte de recherche, filtrer la liste des articles
- Lorsque l'utilisateur change de catégorie, mettre à jour le titre de la page
- Après modification des données, enregistrer dans localStorage
- Lorsque les champs du formulaire changent, effectuer la validation du formulaire
C'est alors que vous avez besoin dewatchetwatchEffect。
watch — Écoute précise
watchÉcoutez les sources de données spécifiées et exécutez une fonction de rappel lorsque les données changent.
Vous pouvez obtenir lanouvelle valeuretl'ancienne valeur, pour un traitement comparatif.
Exemple
import { ref, watch } from 'vue'
const keyword = ref('')
// watch(la source de données écoutée, fonction de rappel)
watch(keyword, (newVal, oldVal) => {
console.log(`Le mot-clé de recherche est passé de "${oldVal}" à "${newVal}"`)
// Exécuter la logique de filtrage ici
})
</script>
<template>
<input v-model="keyword" placeholder="Rechercher des articles...">
<p>Recherche actuelle : {{ keyword }}</p>
</template>
Trois sources de données pour watch
| Type de source de données | Syntaxe | Exemple |
|---|---|---|
| Ref unique | watch(ref, callback) | watch(keyword, fn) |
| Propriété d'un objet réactif | watch(() => obj.prop, callback) | watch(() => user.name, fn) |
| Sources de données multiples | watch([ref1, ref2], callback) | watch([kw, cat], fn) |
Exemple
const keyword = ref('')
const category = ref('All')
// Écouter une seule ref
watch(keyword, (newVal) => {
document.title = newVal ?`Search: ${newVal}` : 'EXAMPLE Blog'
})
// Écouter plusieurs sources de données
watch([mot-clé, catégorie], ([newKw, newCat], [oldKw, oldCat]) => {
console.log('Mot-clé ou catégorie modifiés, re-filtrage des articles')
// Exécuter la logique de filtrage...
})
Par défaut, watch estexécuté paresseusement—il ne se déclenchera pas lorsque les données ont une valeur pour la première fois, uniquement lors des changements suivants. Si vous avez besoin qu'il s'exécute une fois lors de l'initialisation, ajoutez l'
{ immediate: true }option de configuration.
watchEffect — Suit automatiquement les dépendances
watchEffectPas besoin de spécifier manuellement les sources de données ; il suit automatiquement les données réactives utilisées dans le callback.
La plus grande différence avec watch :watchEffect s'exécute immédiatement une première fois。
Exemple
import { ref, watchEffect } from 'vue'
const keyword = ref('')
const articles = ref([
{ id: 1, title: 'Introduction à Vue3' },
{ id: 2, title: 'JS asynchrone' },
])
const filteredArticles = ref([])
// watchEffect suit automatiquement keyword et articles
// Et il s'exécute une fois immédiatement pour initialiser filteredArticles
watchEffect(() => {
// Toutes les données réactives utilisées dans la fonction sont automatiquement suivies
filteredArticles.value = articles.value.filter(
a => a.title.includes(keyword.value)
)
console.log(`Résultat du filtre : ${filteredArticles.value.length} articles`)
})
</script>
<template>
<input v-model="keyword" placeholder="Rechercher">
<div v-for="a in filteredArticles" :key="a.id">
{{ a.title }}
</div>
</template>
Comparaison entre watch et watchEffect
| Fonctionnalité | watch | watchEffect |
|---|---|---|
| Si la source de données est spécifiée | Spécifiée manuellement | Suit automatiquement les dépendances |
| Exécution initiale | Ne s'exécute pas par défaut (configurable avec immediate) | S'exécute immédiatement |
| Obtention de l'ancienne valeur | Peut obtenir l'ancienne valeur | Ne peut pas obtenir l'ancienne valeur |
| Scénarios applicables | Lorsque vous devez comparer les anciennes et nouvelles valeurs et contrôler précisément le moment | Lorsque vous ne vous souciez pas des anciennes/nouvelles valeurs, vous voulez simplement « réagir aux changements » |
Guide de sélection simple : si votre logique est « quand X change, faites Y », utilisez watch. Si votre logique est « dès que l'une des dépendances change, recalculez le résultat », utilisez watchEffect ou computed. En pratique, les données qui peuvent être calculées doivent utiliser computed en priorité ; watch sert à gérer les « effets de bord » — comme appeler des API, sauvegarder localement, manipuler le DOM.
Approche de debounce
Chaque frappe dans le champ de recherche déclenche le filtrage. C'est acceptable pour des données locales, mais si chaque saisie envoie une requête réseau, cela gaspille beaucoup.
DebounceL'idée : attendre que l'utilisateur arrête de taper pendant un certain temps, puis exécuter l'opération.
Exemple
const keyword = ref('')
const filteredArticles = ref([])
let timer = null // ID du minuteur
watch(keyword, (newVal) => {
// Effacer le minuteur précédent
clearTimeout(timer)
// Exécuter le filtrage uniquement s'il n'y a pas de nouveau changement de saisie dans les 300ms
timer = setTimeout(() => {
filteredArticles.value = articles.value.filter(
a => a.title.includes(newVal) || a.summary.includes(newVal)
)
}, 300)
})
Pour ce projet de blog, les données sont locales, donc le debounce n'est pas nécessaire. Mais comprendre cette approche est très utile pour des projets plus importants plus tard.
Mise en pratique : Ajouter une recherche en temps réel au blog
Ajouter une zone de recherche au composant NavBar pour filtrer en temps réel les titres et résumés d'articles par mot-clé.
Exemple
<script setup>
import { ref, computed, onMounted } from 'vue'
import BlogCard from '../components/BlogCard.vue'
import CategoryFilter from '../components/CategoryFilter.vue'
const articles = ref([]) // Tous les articles
const isLoading = ref(true)
const activeCategory = ref('Toutes')
const keyword = ref('') // Mot-clé de recherche
// Extraire toutes les catégories
const categories = computed(() => {
const cats = articles.value.map(a => a.category)
return ['Toutes', ...new Set(cats)]
})
// computed suit automatiquement les modifications de keyword et activeCategory
// Si l'un ou l'autre change, le résultat du filtre sera automatiquement recalculé
const filteredArticles = computed(() => {
let result = articles.value
// Étape 1 : Filtrer par catégorie
if (activeCategory.value !== 'All') {
result = result.filter(a => a.category === activeCategory.value)
}
// Étape 2 : Filtrer par mot-clé (rechercher dans le titre et le résumé)
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
})
// Changer de catégorie n'efface pas la recherche ; les deux fonctionnent en combinaison
function handleCategoryChange(cat) {
activeCategory.value = cat
}
// Charger les données
onMounted(async () => {
try {
const res = await fetch('/posts.json')
articles.value = await res.json()
} catch (err) {
console.error('Load failed', err)
} finally {
isLoading.value = false
}
})
</script>
<template>
<div class="home">
<!-- Boîte de recherche -->
<div class="search-bar">
<input
v-model="keyword"
type="text"
placeholder="Rechercher un titre ou un résumé d'article..."
class="search-input"
/>
<span v-if="keyword" class="clear-btn" @click="keyword = ''">✕</span>
</div>
<!-- Filtre par catégorie -->
<CategoryFilter
:categories="categories"
:active-category="activeCategory"
@update-category="handleCategoryChange"
/>
<!-- Chargement / Vide / Normal -->
<p v-if="isLoading">Chargement...</p>
<p v-else-if="filteredArticles.length === 0">Aucun article correspondant trouvé</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>
Maintenant, la recherche et le filtrage par catégorie fonctionnent ensemble — après avoir saisi un mot-clé de recherche, cliquer sur un bouton de catégorie empilera les filtres. Les deux conditions sont réactives ; lorsque l'une ou l'autre change, filteredArticles se met à jour automatiquement.
Résumé du chapitre
Dans ce chapitre, vous avez maîtrisé watch (spécifier manuellement la source de données, peut obtenir les anciennes et nouvelles valeurs) et watchEffect (suit automatiquement les dépendances, s'exécute immédiatement), et vous avez appris à utiliser computed pour combiner recherche + filtrage par catégorie.
L'expérience de filtrage en temps réel d'une zone de recherche est bien plus fluide que « taper et cliquer sur un bouton » — c'est la valeur d'un système réactif.
Autres extensions