Was Sie durch useEffect beim Abrufen bezahlen müssen und was TanStack Query ändert
Warum das effektbasierte Datenabrufen in React zu doppelten Anfragen, Boilerplate-Code sowie veralteten Closures führt – und wie TanStack Query mit seinen Schlüsseln, staleTime-Parameter sowie Invalidation-Funktionen diese Probleme löst.
Quellen: Stop Using useEffect for API Calls, Use React Query Instead
Das Abrufen von Daten innerhalb von useEffect ist das erste Muster, das die meisten React-Entwickler lernen, und es funktioniert gut für eine einzige Anfrage auf einer einzelnen Seite. Wenn sich die Anwendung weiterentwickelt, führt dasselbe Muster heimlich zu mehreren Anfragen, wiederholt Boilerplate-Code in jedem Komponenten und führt zu subtilen Abhängigkeitsfehlern. Dieser Artikel erklärt, woher diese Kosten stammen und wie TanStack Query (früher React Query) jedes Problem löst, damit Sie entscheiden können, wann Sie wechseln sollten und was Sie nach dem Wechsel konfigurieren müssen.
Das Muster, mit dem alle beginnen
Die vertraute Struktur besteht aus einem Effect mit einem leeren Abhängigkeitsarray, der eine fetch-Funktion aufruft, wenn die Komponente geladen wird:
useEffect(() => {
fetchData();
}, []);
Es erscheint einfach, und genau das ist Teil des Problems. Der Code sagt nichts über Caching, Deduplizierung, Wiederholungsversuche, Stornierung oder darüber, was geschehen soll, wenn der Komponente erneut geladen wird. Jeder dieser Mängel verwandelt sich in einen Fehler oder eine zusätzliche Anfrage, sobald es Dutzende von Endpunkten sowie weitere Lade- und Fehlerzustände zu verfolgen gilt.
Warum die Anfrage zweimal abgesendet wird und das eigentliche Problem dahinter
Eine häufige Überraschung zu Beginn ist, beim Laden der Seite dieselbe Anfrage zweimal im Netzwerk-Tab zu sehen. Im Entwicklungsmodus lädt Reacts StrictMode Komponenten absichtlich neu, um Effekte aufzudecken, die sich nicht selbst bereinigen. Die doppelte Anfrage ist im Entwicklungsmodus beabsichtigt und tritt in einer Produktionsversion nicht auf.
Der doppelte Aufruf ist ein Symptom für ein Problem, das in der Produktion tatsächlich von Bedeutung ist: Ein effektbasiertes Abrufen ist an den Lebenszyklus der Komponente gebunden. Jeder Neuaufbau löst eine neue Anfrage aus, selbst wenn die Daten bereits verfügbar sind. Komponenten, die wiederholt neu aufgebaut werden – wie Tabellenblätter, Modale und Listeinträge – senden ständig Anfragen an den Backend-Server, was die Serverlast erhöht und dazu führt, dass die Benutzeroberfläche langsamer wirkt, als sie eigentlich sein sollte.
TanStack Query trennt die Daten von der Komponente. Die Ergebnisse werden in einem gemeinsamen Cache unter einer Abfrageschlüssel gespeichert. Wenn eine andere Komponente oder dieselbe Komponente nach einem Neuaufbau nach einem Schlüssel fragt, dessen Daten noch als aktuell gelten, gibt die Bibliothek den im Cache gespeicherten Wert aus, ohne das Netzwerk anzufassen. Mehrere Komponenten, die zum gleichen Zeitpunkt denselben Schlüssel anfordern, teilen sich außerdem eine einzige laufende Anfrage.
Manuell verwalteter Lade- und Fehlerzustand
Mit useEffect benötigt jede Anfrage ihr eigenes Trio an Zustumsvariablen für die Daten, das Ladeflag und den Fehler:
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
Wenn man das bei jeder API-Aufruf wiederholt, entsteht eine Menge nahezu identischen Codes, wobei jede Kopie das Risiko von Fehlern birgt. Ein klassisches Beispiel ist es, bei einem Fehler den Wert von loading zu vergessen zurückzusetzen, wodurch ein Ladeindikator weiterhin angezeigt bleibt. Weitere Probleme sind das Nicht ignorieren einer Antwort, die nach dem Entfernen der Komponente eintrifft, sowie das Zulassen, dass eine langsamere frühere Antwort bei schnellen Änderungen der Eingaben eine neuere überschreibt.
useQuery gibt direkt data, error, isLoading und isError (sowie weitere detaillierte Statusfelder) zurück und kümmert sich außerdem um unsortierte Antworten. Der Standardcode in Komponenten entfällt, und die Randfälle werden einmal in einer gut getesteten Bibliothek gelöst, anstatt viele Male im Anwendungscode.
Abhängigkeitsarrays gegenüber Abfrageschlüsseln
Effekte hängen davon ab, dass das Abhängigkeitsarray korrekt gestaltet ist. Wenn ein Wert weggelassen wird, den der Effekt benötigt, entsteht eine veraltete Schließung, die mit unaktuellem Datenmaterial abruft – ein Fehler, dessen Aufspüren Stunden in Anspruch nehmen kann. Wenn hingegen ein Objekt oder eine Funktion bei jeder Neuzeichnung neu erstellt wird, läuft der Effekt bei jeder Neuzeichnung und sendet jeweils eine Anfrage.
TanStack Query ersetzt dieses Raten durch einen klareren Vertrag: Sie geben jeder Abfrage eine Schlüssel und eine Abruffunktion. Der Schlüssel sollte alle Variablen enthalten, von denen die Anfrage abhängt – beispielsweise den Ressourcennamen zusammen mit einer Benutzer-ID oder einem Filter. Wenn sich der Schlüssel ändert, wird eine neue Abfrage ausgeführt; bleibt er unverändert, werden gespeicherte Daten wiederverwendet. Schlüssel werden anhand ihres Inhalts verglichen und nicht per Referenz, sodass das Erstellen eines neuen Arrays bei jeder Darstellung keine zusätzlichen Abrufe verursacht. Die Neuabfrage wird dann durch explizite Richtlinien gesteuert – wie das Einbinden einer Komponente, das Wiedererlangen der Fokussierung des Fensters oder die Wiederverbindung zum Netzwerk – in Kombination mit der von Ihnen konfigurierten staleTime-Einstellung.
Auch wenn Sie weiterhin über Abhängigkeiten nachdenken müssen, drücken Sie diese einmal in einem Schlüssel aus, anstatt ein Array zu verwalten, das stets mit dem Effektinhalt synchron bleiben muss.
Cache, staleTime und Invalidation
Drei Eigenschaften erklären den größten Teil der Unterschiede im täglichen Einsatz.
Der Cache
Nachdem die Daten einer Schlüsselwert-Kombination heruntergeladen wurden, bleiben sie im Cache. Wenn der Benutzer weggeht und zurückkehrt, wird das in Cache gespeicherte Ergebnis sofort angezeigt. Sind die Daten veraltet, lädt die Bibliothek sie im Hintergrund erneut herunter und aktualisiert den Bildschirm, sobald die neue Antwort eintrifft.
staleTime
staleTime bestimmt, wie lange Daten als aktuell gelten. Solange sie aktuell sind, führen weder ein Neuladen noch neue Abonnenten zu einer Anfrage. Der Standardwert ist null, was bedeutet, dass die Daten sofort als veraltet betrachtet werden. Die im Cache gespeicherten Daten werden weiterhin sofort angezeigt, doch ein erneuter Herunterladen findet beim nächsten Neuladen oder Fokussieren des Fensters statt. Die Einstellung eines sinnvollen staleTime für Daten, die nicht jede Sekunde ändern, führt oft zur größten Reduzierung des Backend-Traffics.
invalidateQueries
Nach einer Mutation, wie zum Beispiel beim Aktualisieren eines Profils oder Erstellen eines Beitrags, rufen Sie queryClient.invalidateQueries mit dem entsprechenden Schlüssel auf. Die passenden Abfragen werden als veraltet markiert und alle, die derzeit in Gebrauch sind, werden automatisch neu abgerufen. Es erfolgt keine manuelle Neuerfassung nach jeder Mutation und auch kein manuelles Kopieren der Serverantworten in den lokalen Zustand.
Zusammen decken diese drei Ansätze den größten Teil der Probleme ab, die durch effektbasiertes Abrufen entstehen. Für eine detailliertere Betrachtung der Organisation von Schlüsseln, Abfrageoptionen und Mutationen in großem Maßstab finden Sie unseren Leitfaden zur Strukturierung einer TanStack Query-Datenlage.
Ein typisches Migrationsszenario
Betrachten Sie ein Next.js SaaS-Dashboard mit mehreren Tabellen, wobei jede Tabelle ihre eigenen Daten in einem useEffect lädt. Da das Wechseln der Tabellen die jeweiligen Panels neu initialisiert, führt jeder Tabwechsel zu einer neuen Welle an Anfragen. Der Backend erhält somit weitaus mehr Traffic, als nötig ist, und die Benutzer bemerken eine langsame Ladezeit.
Eine Migration folgt in der Regel drei Schritten:
- Ersetzen Sie jeden
useEffectdurchuseQuerysowie einen Schlüssel, der alle Anfrageparameter enthält. - Weisen Sie jeder Abfrage einen
staleTimezu, der angibt, wie oft ihre Daten tatsächlich aktualisiert werden. - Nach jeder Änderung machen Sie die Schlüssel ungültig, deren Daten betroffen sind.
Das erwartete Ergebnis ist ein flüssigeres Dashboard, weniger Backend-Aufrufe sowie eine bessere Benutzererfahrung – bei gleichzeitig weniger Code in jeder Komponente.
Wo useEffect weiterhin sinnvoll ist
Dies ist kein Fall für das vollständige Entfernen von useEffect. Es bleibt das richtige Werkzeug, um mit Elementen außerhalb von React zu synchronisieren, die keine Anfrage-und-Antwort-Daten sind: Abonnements, DOM-Event-Listener, Timer und Widgets von Drittanbietern. Für API-Aufrufe in einer wachsenden Anwendung ist jedoch eine spezielle Bibliothek für den Serverzustand besser geeignet, da sie die Last auf der Backend-Seite verringert, Overhead entfernt und Caching sowie Invalidation direkt bereitstellt.
Auch in Fällen, in denen eine Bibliothek über das Notwendige hinausgeht – beispielsweise bei einer kleinen Anwendung mit nur einem oder zwei Anfragen – kann ein kleiner, maßgeschneiderter Hook mit korrekter Abbruchfunktion ausreichen. Unser Artikel zu einem minimalen useFetch Hook und seinen Grenzen behandelt diesen Kompromiss. Falls Sie unsicher sind, migrieren Sie zunächst eine Seite und vergleichen Sie die Anfragenzahlen im Netzwerk-Tab.
Hauptpunkte
- Die doppelte Anfrage in der Entwicklung entsteht durch StrictMode; das Problem in der Produktion besteht darin, dass fetch-Befehle aufgrund von Effects bei jedem Neuladen wiederholt werden.
- Eine Query-Key ersetzt den Abhängigkeitsarray: Fügen Sie alle Eingaben hinzu, von denen die Anfrage abhängt – dadurch werden Caching und Neuanfragen automatisch gesteuert.
- Der Standardwert von
staleTimeist null, daher sollten Sie ihn gezielt festlegen, wenn Sie Hintergrund-Neuanfragen beim Neuladen vermeiden möchten.
useEffect zur Synchronisierung mit externen Systemen, nicht zum Laden von Serverdaten.