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
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
| Syntax | Ausführungszeitpunkt | Anwendungsfall |
|---|---|---|
useEffect(fn) | Wird nach jedem Rendern ausgeführt | Selten verwendet; anfällig für Endlosschleifen |
useEffect(fn, []) | Wird nur einmal nach dem ersten Rendern ausgeführt | Laden von Daten, Initialisieren von Drittanbieter-Bibliotheken |
useEffect(fn, [dep]) | Läuft nach dem initialen Rendern + wenn sich dep ändert | Auf 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
[
{
"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
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
// 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
}, [])
| Seiteneffekt | Bereinigung nötig? | Bereinigungsmethode |
|---|---|---|
| fetch-Anfrage | Ja | Verwende cancelled-Flag oder AbortController |
| Event-Listener | Ja | removeEventListener |
| Timer | Ja | clearInterval / clearTimeout |
| DOM ändern und dann wiederherstellen | Ja | Ursprünglichen Zustand bei der Bereinigung wiederherstellen |
| localStorage-Schreibvorgänge | nein | Keine Bereinigung erforderlich |
Vier Zustände für die Behandlung asynchroner Anfragen
Jede Datenanfrage sollte die folgenden vier UI-Zustände abdecken:
| State | Bedingung | UI |
|---|---|---|
| Wird geladen | isLoading === true | Ladeanimation, Skeleton-Screen |
| Erfolg | Daten wurden zurückgegeben | Inhalt normal rendern |
| Fehler | error !== null | Fehlermeldung + Wiederholen-Button |
| Leere Daten | Daten sind ein leeres Array | Leerzustandsmeldung |
Vergleich mit Vue3-Lebenszyklus
| Vue3 | React | Beschreibung |
|---|---|---|
onMounted | useEffect(fn, []) | Wird nach dem ersten Rendern ausgeführt |
watch(keyword, fn) | useEffect(fn, [keyword]) | Reagiert auf bestimmte Datenänderungen |
onBeforeUnmount | useEffect 的 cleanup | Bereinigung vor der Zerstörung der Komponente |
| Kein direktes Gegenstück | useEffect(() => { 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