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

import { useState, useEffect } from 'react'

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:

MethodeAnsatzAnwendungsszenario
Event-Handler-FunktionDirekt in onChange filternEinfache Logik, nur an einer Stelle ausgelöst
useEffect-ReaktionuseEffect hängt von keyword abMehrere 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

import { useState, useEffect } from 'react'

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

// Dateipfad: src/pages/HomePage.jsx
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

/* Stile für das Suchfeld (an App.css anhängen) */
.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