Seiteneffekte und Datenabruf — useEffect

In diesem Kapitel lernst du den wichtigsten Seiteneffekt-Hook von React kennen — useEffect — und verwendest fetch, um Blog-Daten zum richtigen Zeitpunkt aus einer JSON-Datei zu laden.


Was ist ein Seiteneffekt?

In React bezieht sichSide Effectauf Vorgänge, die nichts mit dem Rendering zu tun haben:

  • Daten vom Server anfordern
  • Manipulieren von localStorage
  • Timer mit setInterval setzen
  • Das DOM manuell verändern
  • Externe Ereignisse abonnieren

Seiteneffekte können nicht direkt im Funktionskörper der Komponente ausgeführt werden (was dazu führen würde, dass sie bei jedem Rendern ausgeführt werden); sie müssen inuseEffect platziert werden.


Grundlegende Verwendung von useEffect

Beispiel

import { useState, useEffect } from 'react'

function HomePage() {
  const [message, setMessage] = useState('Lädt...')

  // useEffect(fn, deps): führt den Seiteneffekt aus, nachdem die Komponente gerendert wurde
  useEffect(() => {
    console.log('Die Komponente wurde in das DOM eingebunden; Seiteneffekte können jetzt ausgeführt werden.')
    setMessage('Datenladen abgeschlossen!')
  }, [])  // Leeres Abhängigkeitsarray = wird nur einmal nach dem ersten Rendern ausgeführt

  return <p>{message}</p>
}

Drei Möglichkeiten, das Abhängigkeitsarray zu schreiben

SyntaxAusführungszeitpunktAnwendungsfall
useEffect(fn)Wird nach jedem Rendern ausgeführtSelten verwendet; anfällig für Endlosschleifen
useEffect(fn, [])Wird nur einmal nach dem ersten Rendern ausgeführtLaden von Daten, Initialisieren von Drittanbieter-Bibliotheken
useEffect(fn, [dep])Läuft nach dem initialen Rendern + wenn sich dep ändertAuf bestimmte Datenänderungen hören und entsprechende Operationen ausführen

Unterschied zwischen dem Weglassen des Abhängigkeitsarrays vs. eines leeren Arrays:useEffect(fn)Läuft nach jedem Rendern;useEffect(fn, [])Er wird nur einmal nach dem initialen Rendern ausgeführt. Ersteres kann leicht Endlosschleifen verursachen (setState im Effekt → löst ein Rendern aus → Effekt läuft erneut → ...), daher wird es fast nie verwendet.


JSON-Daten mit fetch laden

Genau wie bei Vue3 legen wir die Artikeldaten in diepublic/posts.jsonDatei.

Schritt 1: Die Datendatei erstellen

Beispiel

// Dateipfad: public/posts.json
[
  {
    "id": 1,
"title": "Der vollständige Leitfaden für den Einstieg mit React",
"summary": "React Hooks von Grund auf lernen, einschließlich Kernkonzepten wie useState und useEffect.",
"content": "<h2>Warum React lernen?</h2><p>React ist eines der beliebtesten Frontend-Frameworks...</p>",
    "category": "React",
    "date": "2024-05-10"
  },
  {
    "id": 2,
"title": "Asynchrone Programmierung in JavaScript erklärt",
"summary": "Verstehen Sie Promise, async/await, Event Loop und Microtask-Queue in einem Artikel.",
"content": "<h2>Was ist asynchron?</h2><p>JS ist single-threaded...</p>",
    "category": "JavaScript",
    "date": "2024-05-08"
  },
  {
    "id": 3,
"title": "CSS Grid Layout in der Praxis",
"summary": "Komplexe responsive Layouts lassen sich mit CSS Grid einfach umsetzen.",
"content": "<h2>Erste Schritte mit Grid</h2><p>Grid ist ein zweidimensionales Layout-System...</p>",
    "category": "CSS",
    "date": "2024-05-05"
  }
]

Schritt 2: Daten in useEffect laden

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([])   // Artikeldaten
  const [isLoading, setIsLoading] = useState(true) // Ladezustand
  const [error, setError] = useState(null)        // Fehlermeldung
  const [activeCategory, setActiveCategory] = useState('Alle')

  // useEffect lädt Daten: leeres Abhängigkeitsarray = wird nur einmal nach dem initialen Rendern ausgeführt
  useEffect(() => {
    let cancelled = false  // Flag, um setState nach dem Unmounten der Komponente zu verhindern

    async function fetchPosts() {
      setIsLoading(true)
      setError(null)
      try {
        const res = await fetch('/posts.json')
        if (!res.ok) throw new Error(`HTTP ${res.status}`)
        const data = await res.json()
        if (!cancelled) {
          setArticles(data)
        }
      } catch (err) {
        if (!cancelled) {
          setError(err.message)
        }
      } finally {
        if (!cancelled) {
          setIsLoading(false)
        }
      }
    }

    fetchPosts()

    // Cleanup-Funktion: cancelled auf true setzen, wenn die Komponente ausgehängt wird
    return () => { cancelled = true }
  }, [])

  const categories = useMemo(() => {
    return ['Alle', ...new Set(articles.map(a => a.category))]
  }, [articles])

  const filteredArticles = useMemo(() => {
    if (activeCategory === 'All') return articles
    return articles.filter(a => a.category === activeCategory)
  }, [articles, activeCategory])

  return (
    <div>
      <h2 className="section-title">Neueste Artikel</h2>

      {/* Laden */}
      {isLoading && <p className="status-msg">Wird geladen, bitte warten...</p>}

      {/* Ladefehler */}
      {error && (
        <div className="status-msg error">
          <p>Laden fehlgeschlagen:{error}</p>
          <button onClick={() => window.location.reload()}>Erneut versuchen</button>
        </div>
      )}

      {/* Laden der Daten abgeschlossen */}
      {!isLoading && !error && (
        <>
          <CategoryFilter
            categories={categories}
            activeCategory={activeCategory}
            onCategoryChange={setActiveCategory}
          />
          <p className="result-info">common{filteredArticles.length}Artikel</p>
          {filteredArticles.length === 0 ? (
            <p className="empty-tip">Noch keine Artikel in dieser Kategorie</p>
          ) : (
            <div className="article-grid">
              {filteredArticles.map(article => (
                <BlogCard key={article.id} {...article} />
              ))}
            </div>
          )}
        </>
      )}
    </div>
  )
}

export default HomePage

cancelledDas Flag ist ein übliches Muster in React. Wenn fetch noch läuft und der Benutzer die Seite verlässt (Komponente wird ausgehängt), meldet das nachfolgende setState eine Fehlerwarnung. Indem man es in der Cleanup-Funktion setzt,cancelled = truekann setState nach dem Aushängen übersprungen werden.


Cleanup-Funktion

Die Callback-Funktion von useEffect kann eine Funktion zurückgeben, die man alsCleanup-Funktion bezeichnet。

Sie wird zu zwei Zeitpunkten ausgeführt:Wenn die Komponente ausgehängt wirdundBevor der nächste Effekt ausgeführt wird。

Beispiel

useEffect(() => {
  // Seiteneffekt: Events abonnieren
  const handleScroll = () => console.log('Gescrollt')
  window.addEventListener('scroll', handleScroll)

  // Die Bereinigungsfunktion zurückgeben
  return () => {
    // Den Event-Listener automatisch entfernen, wenn die Komponente unmountet wird, um Speicherlecks zu verhindern
    window.removeEventListener('scroll', handleScroll)
  }
}, [])

useEffect(() => {
  // Seiteneffekt: einen Timer setzen
  const timer = setInterval(() => {
    console.log('Wird jede Sekunde ausgeführt')
  }, 1000)

  return () => clearInterval(timer)  // Den Timer löschen
}, [])
SeiteneffektBereinigung nötig?Bereinigungsmethode
fetch-AnfrageJaVerwende cancelled-Flag oder AbortController
Event-ListenerJaremoveEventListener
TimerJaclearInterval / clearTimeout
DOM ändern und dann wiederherstellenJaUrsprünglichen Zustand bei der Bereinigung wiederherstellen
localStorage-SchreibvorgängeneinKeine Bereinigung erforderlich

Vier Zustände für die Behandlung asynchroner Anfragen

Jede Datenanfrage sollte die folgenden vier UI-Zustände abdecken:

StateBedingungUI
Wird geladenisLoading === trueLadeanimation, Skeleton-Screen
ErfolgDaten wurden zurückgegebenInhalt normal rendern
Fehlererror !== nullFehlermeldung + Wiederholen-Button
Leere DatenDaten sind ein leeres ArrayLeerzustandsmeldung

Vergleich mit Vue3-Lebenszyklus

Vue3ReactBeschreibung
onMounteduseEffect(fn, [])Wird nach dem ersten Rendern ausgeführt
watch(keyword, fn)useEffect(fn, [keyword])Reagiert auf bestimmte Datenänderungen
onBeforeUnmountuseEffect 的 cleanupBereinigung vor der Zerstörung der Komponente
Kein direktes GegenstückuseEffect(() => { fn; return cleanup }, [dep])Wenn sich dep ändert, zuerst Bereinigung ausführen, dann fn

Kapitelzusammenfassung

In diesem Kapitel hast du die Kernverwendung von useEffect gemeistert: die drei Arten, das Abhängigkeitsarray zu schreiben, das asynchrone Laden von Daten mit fetch, den Umgang mit den vier UI-Zuständen und die Rolle der Cleanup-Funktion.

Jetzt werden die Blogdaten dynamisch aus einer externen JSON-Datei geladen, nicht mehr in JS hartcodiert.

Andere Erweiterungen