Fortgeschrittenes useEffect — Echtzeitsuche
In diesem Kapitel tauchen Sie in den Mechanismus zur Änderung der Abhängigkeiten von useEffect ein, um Echtzeitfilterung und Debounce-Optimierung für das Suchfeld zu implementieren.
Auslöser bei Änderung der useEffect-Abhängigkeit
Wenn sich ein Wert im useEffect-Abhängigkeitsarray ändert, führt React den Effekt erneut aus.
Beispiel
function SearchDemo() {
const [keyword, setKeyword] = useState('')
const [results, setResults] = useState([])
// Wenn sich keyword ändert, Filterung erneut ausführen
useEffect(() => {
console.log('keyword geändert zu:', keyword)
// Hier kannst du die Suchlogik ausführen
setResults(/* Gefilterte Ergebnisse */)
}, [keyword]) // Hängt von keyword ab
return (
<div>
<input
value={keyword}
onChange={e => setKeyword(e.target.value)}
placeholder="Suchen..."
/>
<p>Aktuelle Suche:{keyword}</p>
</div>
)
}
Dieses Muster eignet sich, um einen State zu überwachen und bei Änderung automatisch die entsprechende Operation auszuführen.
Handhabung in Events vs. useEffect
Es gibt zwei Möglichkeiten, die Suchfilterung zu implementieren:
| Methode | Ansatz | Anwendungsszenario |
|---|---|---|
| Event-Handler-Funktion | Direkt in onChange filtern | Einfache Logik, nur an einer Stelle ausgelöst |
| useEffect-Reaktion | useEffect hängt von keyword ab | Mehrere Datenquellen können das Filterergebnis beeinflussen |
Im Blog-Szenario ist die Filterlogik für die Eingabe im Suchfeld sehr einfach,Es ist passender, sie direkt in einen Event-Handler oder useMemo zu legen.。
useEffect ist besser geeignet für Szenarien, die mit externen Systemen synchronisiert werden müssen (z. B. Aufrufen von APIs, Speichern in localStorage).
Debounce-Optimierung
Wenn jeder Tastendruck eine Suche auslöst (lokales Filtern ist in Ordnung, aber wenn das Senden von Netzwerkanfragen damit verbunden ist), würde das viel Verschwendung verursachen.
DebounceDie Idee: Nachdem der Benutzer für eine gewisse Zeit aufgehört hat zu tippen, wird die Operation ausgeführt.
Beispiel
function useDebounce(value, delay = 300) {
const [debouncedValue, setDebouncedValue] = useState(value)
useEffect(() => {
// Timer setzen: debouncedValue nach delay ms aktualisieren
const timer = setTimeout(() => {
setDebouncedValue(value)
}, delay)
// Cleanup-Funktion: wenn sich value innerhalb von delay erneut ändert, den vorherigen Timer löschen
return () => clearTimeout(timer)
}, [value, delay])
return debouncedValue
}
// Debounce verwenden
function SearchInput() {
const [keyword, setKeyword] = useState('')
const debouncedKeyword = useDebounce(keyword, 300)
// Nur ausführen, wenn sich der entprellte Wert ändert (z. B. eine Anfrage senden)
useEffect(() => {
if (debouncedKeyword) {
console.log('Suche wird tatsächlich ausgeführt:', debouncedKeyword)
// fetch(`/api/search?q=${debouncedKeyword}`)
}
}, [debouncedKeyword])
return (
<input
value={keyword}
onChange={e => setKeyword(e.target.value)}
placeholder="Suchen..."
/>
)
}
Das Prinzip von Debounce: Jede Eingabe setzt einen 300ms-Timer zurück. Nur wenn innerhalb von 300ms keine neue Eingabe erfolgt, wird der Timer ausgeführt und debouncedKeyword aktualisiert. Auf diese Weise wird die Suche nicht häufig ausgelöst, wenn der Benutzer schnell tippt.
Praxis: Echtzeitsuche zum Blog hinzufügen
Beispiel
import { useState, useEffect, useMemo } from 'react'
import BlogCard from '../components/BlogCard'
import CategoryFilter from '../components/CategoryFilter'
function HomePage() {
const [articles, setArticles] = useState([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
const [activeCategory, setActiveCategory] = useState('Alle')
const [keyword, setKeyword] = useState('') // Suchbegriff
// Daten laden
useEffect(() => {
let cancelled = false
async function fetchPosts() {
setIsLoading(true)
try {
const res = await fetch('/posts.json')
const data = await res.json()
if (!cancelled) setArticles(data)
} catch (err) {
if (!cancelled) setError(err.message)
} finally {
if (!cancelled) setIsLoading(false)
}
}
fetchPosts()
return () => { cancelled = true }
}, [])
const categories = useMemo(() => {
return ['Alle', ...new Set(articles.map(a => a.category))]
}, [articles])
// Filtern + suchen: sowohl nach Kategorie als auch nach Suchbegriff filtern
const filteredArticles = useMemo(() => {
let result = articles
// Zuerst nach Kategorie filtern
if (activeCategory !== 'All') {
result = result.filter(a => a.category === activeCategory)
}
// Dann nach Suchbegriff filtern
if (keyword.trim()) {
const kw = keyword.trim().toLowerCase()
result = result.filter(a =>
a.title.toLowerCase().includes(kw) ||
a.summary.toLowerCase().includes(kw)
)
}
return result
}, [articles, activeCategory, keyword])
return (
<div>
<h2 className="section-title">Neueste Artikel</h2>
{/* Suchfeld */}
<div className="search-bar">
<input
type="text"
value={keyword}
onChange={e => setKeyword(e.target.value)}
placeholder="Artikelüberschriften oder Zusammenfassungen durchsuchen..."
className="search-input"
/>
{keyword && (
<span className="clear-btn" onClick={() => setKeyword('')}>✕</span>
)}
</div>
{isLoading && <p className="status-msg">Wird geladen...</p>}
{error && (
<div className="status-msg error">
<p>Fehler beim Laden:{error}</p>
<button onClick={() => window.location.reload()}>Erneut versuchen</button>
</div>
)}
{!isLoading && !error && (
<>
<CategoryFilter
categories={categories}
activeCategory={activeCategory}
onCategoryChange={setActiveCategory}
/>
<p className="result-info">
common{filteredArticles.length}Artikel
{keyword &&`, Suche nach "${keyword}」`}
</p>
{filteredArticles.length === 0 ? (
<p className="empty-tip">Keine passenden Artikel gefunden</p>
) : (
<div className="article-grid">
{filteredArticles.map(article => (
<BlogCard key={article.id} {...article} />
))}
</div>
)}
</>
)}
</div>
)
}
export default HomePage
Beispiel
.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: #1976d2;
}
.clear-btn {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: #999;
font-size: 18px;
}
.status-msg {
text-align: center;
padding: 60px 0;
font-size: 16px;
color: #999;
}
.status-msg.error {
color: #e74c3c;
}
.status-msg.error button {
margin-left: 10px;
padding: 4px 12px;
cursor: pointer;
}
Suche und Kategoriefilterung können gleichzeitig funktionieren – nach Eingabe eines Schlüsselworts werden durch Klicken auf eine Kategorie-Schaltfläche beide Bedingungen zum Filtern angewendet.
Kapitelzusammenfassung
In diesem Kapitel hast du dich mit dem Mechanismus der Abhängigkeitsänderung von useEffect befasst: Der Effekt wird erneut ausgeführt, wenn sich Werte im Abhängigkeitsarray ändern, das Prinzip und die Implementierung von Debounce (setTimeout + Cleanup) sowie die Abgrenzung bei der Auswahl zwischen useEffect und Event-Handlern.
Die Echtzeitfilterung im Suchfeld fühlt sich flüssig an – das ist der Wert der deklarativen Programmierung: Du deklarierst die Berechnungsregel für filteredArticles, und React berechnet sie automatisch neu, wenn sich ihre Abhängigkeiten ändern.
Andere Erweiterungen