React möchte, dass Ihre Komponenten vorhersehbar sind. Geben Sie ihr denselben State und dieselben Props, und sie sollte jedes Mal die gleiche UI zeichnen. Aber die meisten realen Anwendungen können in dieser Blase nicht überleben. Sie müssen nach außen greifen. Ein Dashboard benötigt aktuelle Zahlen von einem Server. Ein Chat-Widget muss auf Nachrichten hören. Ein Timer muss ticken. Diese Operationen sind Side Effects, und sie liegen außerhalb von Reacts Render-Zyklus. Der useEffect-Hook ist der Ort, an dem Sie diese unordentliche, unvorhersehbare Arbeit auslagern, damit Ihre Komponente selbst integer bleibt.
Side Effects: Was in useEffect gehört
Ein Side Effect ist alles, was die Welt außerhalb der Rückgabe von JSX beeinflusst. Die Render-Phase von React sollte rein (pure) sein. Wenn Sie mit dem Abrufen von Daten, dem Schreiben in globale Variablen oder dem Anhängen von Listenern an das DOM beginnen, verlassen Sie das reine Terrain.
Häufige Beispiele sind:
- Abrufen von Daten von einer API
- Einrichten von Timern oder Intervallen
- Hinzufügen von Event-Listenern zu
windowoderdocument - Aktualisieren des Browser-Tab-Titels
- Verbinden mit WebSockets
Diese Aufgaben haben eines gemeinsam: Sie gehören weder in das return-Statement Ihrer Komponente noch in die Haupt-Render-Logik. Der Versuch, eine Browser-API wie setInterval direkt im Render-Body aufzurufen, führt bei jedem Render-Vorgang zur Ausführung, was duplizierte Timer und unvorhersehbares Verhalten zur Folge hat. useEffect existiert genau dazu, diese Arbeit zu isolieren und sie zum richtigen Zeitpunkt auszuführen.
Wie das Dependency Array das Timing steuert
Das zweite Argument von useEffect ist das Dependency Array, und es ist die größte Quelle für Verwirrung bei Entwicklern, die von Class Components kommen. Betrachten Sie es als eine Menge von Variablen, die React beobachtet, um zu entscheiden, ob Ihr Effect nach dem aktuellen Render übersprungen oder ausgeführt werden soll.
Es gibt drei Muster, die Sie immer wieder verwenden werden.
Gar kein Dependency Array. Wenn Sie das Array ganz weglassen, geht React davon aus, dass der Effect nach jedem einzelnen Render ausgeführt werden soll, einschließlich des ersten. Das ist selten das, was Sie benötigen. Wenn Ihr Effect eine Netzwerkanfrage oder eine schwere DOM-Operation durchführt, wird das Ausführen bei jedem Tastendruck oder jeder State-Änderung die Performance ruinieren. Verwenden Sie dieses Muster nur, wenn Sie wirklich etwas neu ausführen müssen, weil sich irgendeine Prop oder jeder State geändert haben könnte und Sie nicht spezifizieren können, welche es sind.
Ein leeres Array []. Dies weist React an, den Effect genau einmal auszuführen, unmittelbar nachdem die Komponente gemountet wurde und das DOM bereit ist. Dies ist der richtige Ort für initiale Datenabrufe. Wenn Ihre Komponente beispielsweise Benutzerprofildaten lädt, möchten Sie, dass diese Anfrage genau einmal ausgeführt wird, wenn die Profilseite erscheint, und nicht jedes Mal, wenn der Benutzer mit einem Formular weiter unten auf der Seite interagiert.
Ein Array mit spezifischen Variablen [count]. Dies ist das Präzisionswerkzeug. React vergleicht die aktuellen Werte dieser Abhängigkeiten mit ihren Werten während des letzten Renders. Wenn sich einer von ihnen geändert hat, wird der Effect ausgeführt. Wenn sich nichts in der Liste geändert hat, überspringt React den Effect komplett.
Wenn Sie den Browser-Tab-Titel mit einer State-Variable synchronisieren, würden Sie diese Variable in das Dependency Array aufnehmen. React aktualisiert den Titel dann nur, wenn sich dieser Wert ändert. Lassen Sie sie weg, bleibt der Titel veraltet. Geben Sie nicht verwandte State-Variablen an, verschwenden Sie Rechenzyklen, indem Sie den Titel für Änderungen aktualisieren, die keine Rolle spielen.
Cleanup ist nicht optional
Einige Effects hinterlassen Spuren. Ein Timer läuft weiter. Ein Event-Listener feuert weiter. Eine WebSocket-Verbindung bleibt offen. Wenn Ihre Komponente unmountet, oder selbst wenn ein Effect neu ausgeführt wird, weil sich seine Abhängigkeiten geändert haben, räumt React die Rückstände des vorherigen Effects nicht automatisch auf. Das ist Ihre Aufgabe.
Sie erstellen eine Cleanup-Funktion, indem Sie eine Funktion aus dem useEffect zurückgeben. React ruft dieses Cleanup auf, bevor es den nächsten Effect anwendet, und noch einmal, wenn die Komponente den Bildschirm verlässt.
Sie sollten Cleanup verwenden für:
- Das Leeren von Intervallen oder Timeouts mit
clearIntervaloderclearTimeout - Das Entfernen von Event-Listenern, die zu
window,documentoder externen Knoten hinzugefügt wurden - Das Abmelden (Unsubscribe) von Datenströmen oder Diensten
Vernachlässigen Sie dies, und Sie erhalten Memory Leaks. Eine Komponente wird gemountet, fügt einen Scroll-Listener hinzu, unmountet, und der Listener bleibt bestehen. Der Browser behält den Callback und die darauf referenzierten DOM-Knoten im Speicher. Mit der Zeit, besonders in Single-Page-Anwendungen mit viel Navigation, häufen sich diese "Geister" an und verlangsamen den Tab. Die Lösung besteht meist nur aus wenigen Zeilen: Geben Sie eine Funktion zurück, die das Entfernt, was Sie hinzugefügt haben.
Häufige Fehler, die in die Produktion gelangen
Selbst erfahrene Entwickler greifen zu useEffect, wenn es eine einfachere Option gibt. Hier sind drei Muster, die während eines Code-Reviews ein Warnsignal darstellen sollten.
Endlosschleifen. Aktualisieren Sie niemals eine State-Variable innerhalb von useEffect, wenn dieselbe Variable in Ihrem Dependency-Array enthalten ist, es sei denn, Sie haben eine Bedingung, die den Zyklus unterbricht. Wenn Sie count auslesen, es erhöhen und count als Dependency angeben, erkennt React die Änderung, führt ein Re-Rendering durch, führt den Effect erneut aus, erhöht den Wert wieder und blockiert den Browser.
Unnötige Effects. Verwenden Sie useEffect nicht, um einen Wert aus bestehenden Props oder dem State zu berechnen. Wenn Sie diesen Wert direkt während des Renders ableiten können, tun Sie das einfach. Abgeleitete Werte gehören in den Body der Komponente oder in eine memoisierte Berechnung mit useMemo. Sie in einen Effect zu verschieben, verteilt Ihre Logik ohne jeglichen Nutzen auf die Render- und Effect-Phasen und macht den Code schwerer nachvollziehbar.
Das falsche Werkzeug für Benutzeraktionen. use
