React-Zustand neu betrachten: Wo Ihre Daten tatsächlich gespeichert werden sollten
Weniger React-Bugs durch weniger useState: Übertragen Sie den Zustand in die URL, das DOM oder abgeleitete Werte und entscheiden Sie vor dem Einsatz von State-Management, wer für jeden Teil des Zustands verantwortlich ist.
Quellen: Stop Adding React State: It’s Creating Bugs You Don’t Need
Ein Vertriebsmitarbeiter in einem wachsenden SaaS-Unternehmen verbringt einen großen Teil des Vormittags damit, in einer Kundendatenbank mit zehntausend Konten zu suchen.
Nach sorgfältigem Filtern isoliert er die in Europa ansässigen Unternehmenskunden, deren Abonnements in den letzten neunzig Tagen nicht erneuert wurden.
Das Ergebnis ist eine präzise Liste von dreißig Konten, die sofortige Aufmerksamkeit benötigen.
Er nimmt die URL direkt aus dem Browser und sendet sie über Slack an seinen Regionalleiter zusammen mit einer Notiz:
"Hier sind die prioritären Konten für diesen Morgen."
Der Leiter öffnet den Link.
Die Seite wird angezeigt.
Doch anstelle der gefilterten Liste erscheint die Standardansicht: alle zehntausend Konten, ungefiltert und unsortiert.
All diese sorgfältige Filterung verschwand, weil der Entwickler, der die Seite erstellt hatte, jeden Filter, jedes Sortierfeld sowie jeden Paginierungswert im Zustand des lokalen Komponenten-Objekts mithilfe von useState gespeichert hatte.
Dieses Szenario veranschaulicht ein Muster, das in React-Codebasen ständig vorkommt.
Wir neigen dazu, den Zustand zu nutzen, einfach weil React es so einfach macht, ihn zu erstellen.
Braucht man einen Filterwert?
useState.
Muss man tracken, welche Registerkarte aktiv ist?
useState.
Braucht man einen Zähler?
useState.
Muss man ein Formfeld speichern?
useState.
Muss man die verschiedenen Phasen eines API-Aufrufs verfolgen?
Dann sind das wieder drei weitere useState-Aufrufe.
Bald schon muss eine einzige Komponente Dutzende von Werten verwalten, die ursprünglich gar nicht unbedingt im React-Zustand vorhanden sein mussten.
Der Zustand an sich ist hier nicht das Problem. Jede anspruchsvolle React-Anwendung benötigt ihn.
Das eigentliche Problem ist es, Zustand an einen Ort zu platzieren, an dem er nicht hingehört.
Jede zusätzliche Zustandsvariable bedeutet, dass die Anwendung alles mit ihr in Einklang bringen muss.
Lässt man das unkontrolliert zu, entstehen zahlreiche Probleme, darunter:
- Zusätzliche Arbeit beim Abstimmen doppelter Werte
- Daten, die irgendwo im Cache veralten
- Auf dem Bildschirm angezeigte Werte, die nicht mehr der Realität entsprechen
- Rendern, das ausgelöst wird, obwohl sich eigentlich nichts Wesentliches geändert hat
- Zeitprobleme, bei denen zwei Aktualisierungen unvorhersehbar kollidieren
- Effekte, die immer komplizierter werden, um alles konsistent zu halten
- Bugs, bei denen es viel länger dauert, ihren Ursprung zu finden
- Eine Seite, die alles vergisst, sobald sie neu geladen wird
Exzellentes React-Programmieren bedeutet nicht unbedingt, komplexe Zustandslogik zu meistern.
Oft geht es darum, zu erkennen, wann man diesen Zustand überhaupt nicht erst erzeugen sollte.
1. Die URL ist der beste Zustandsmanager Ihrer Anwendung
Stellen Sie sich einen typischen, datenreichen Anwendungsbildschirm vor.
Er benötigt vermutlich die Überwachung folgender Aspekte:
- Suche
- Filter
- Sortierung
- Seiteneinstellungen
- Aktive Tabellen
- Ausgewählte Kategorien
- Datumspannen
Der übliche Ansatz besteht darin, all das in den Zustand der Komponente zu speichern.
function ProductList() {
const [activeCategory, setActiveCategory] = useState('all');
const [currentPage, setCurrentPage] = useState(1);
const [searchQuery, setSearchQuery] = useState('');
// Fetch and filter data...
}
Auf den ersten Blick scheint das völlig in Ordnung zu sein.
Dann klickt der Benutzer auf „Erneuern“.
Jeder Wert kehrt in seinen Standardzustand zurück.
Oder sie teilen den Link mit einem Kollegen.
Der Kollege gelangt zu einer völlig anderen Ansicht.
Oder sie klicken auf die „Zurück“-Schaltfläche des Browsers.
Die eingestellte Filterung kehrt möglicherweise nicht zurück, weil der React-State keine eingebaute Verbindung zur Navigationsgeschichte des Browsers hat.
useState selbst ist hier nicht schuld.
Das eigentliche Problem ist, dass die URL bereits als Mechanismus zur Darstellung des Navigationszustands existiert und völlig umgangen wird.
Anstatt Folgendes zu schreiben:
const [currentPage, setCurrentPage] = useState(1);
const [searchQuery, setSearchQuery] = useState('');
const [category, setCategory] = useState('all');
könnte man dieselben Informationen direkt in der URL kodieren.
Etwas wie:
/products?category=enterprise&page=3&query=react
Mit einer Routing-Bibliothek wie Next.js, React Router oder TanStack Router kann man diese Parameter direkt aus der URL lesen:
import { useSearchParams } from 'next/navigation';
function ProductList() {
const searchParams = useSearchParams(); const category = searchParams.get('category') || 'all';
const page = Number(searchParams.get('page')) || 1;
const query = searchParams.get('query') || ''; // Fetch data using the URL state...
}
Diese kleine Änderung verändert den Lebenszyklus dieses Zustands erheblich.
Seite aktualisieren?
Die Filter bleiben unverändert.
Den Link teilen?
Jeder, der ihn öffnet, sieht genau denselben Anblick.
Als Lesezeichen speichern?
Später wird die Seite wieder in diesem Zustand geladen.
Zurückgehen?
Der Browser wechselt korrekt zu den vorherigen Zuständen.
Das ist ein bedeutender Vorteil in der Architektur.
Der URL-Zustand bietet mehr als nur Persistenz
Dass die Seite nach einer Aktualisierung erhalten bleibt, ist nur ein Teil des Vorteils.
Der größere Vorteil besteht darin, dass die URL zu einem portablen Schnappschuss des aktuellen Zustands Ihrer Anwendung wird, den man mit jedem teilen kann.
Nehmen wir ein Kundenservice-Dashboard als Beispiel:
/support/tickets?status=open&priority=high&team=billing
Ein Manager kann diesen genauen Link sofort an einen anderen Manager senden.
Ein auf Verkäufe ausgerichtetes Dashboard könnte so aussehen:
/accounts?region=europe&plan=enterprise&renewal=90
Ein Analysetool könnte etwas Ähnliches verwenden:
/analytics?range=30d&metric=revenue&groupBy=region
Keine dieser Abfrageseiten sind willkürlich gewählt.
Jede von ihnen ist ein Deep Link, der auf einen bestimmten, rekonstruierbaren Zustand der Anwendung verweist.
Dafür wurden URLs schließlich erstellt.
Wann URL-Zustände sinnvoll sind
URL-Parameter eignen sich hervorragend für Dinge wie:
- Was der Benutzer in ein Suchfeld eingegeben hat
- Aktive Filterauswahlen
- Die Spalten und Sortierrichtungen
- Welche Ergebnisseite angezeigt wird
- Die derzeit ausgewählte Registerkarte
- Ein gewählter Zeitraum
- Welcher gespeicherte Ansicht modifiziert wird
- Einstellungen für einen generierten Bericht
- Individuelle Konfigurationen auf einem Dashboard
- Jeden UI-Zustand, den ein Benutzer an jemand anderen weitergeben möchte
Trotzdem gehört nicht alles dorthin.
Passwörter sollten offensichtlich nicht in der URL enthalten sein.
Auch kein vorübergehender Zustand einer Modal-Animation sollte gespeichert werden.
Ebenso wenig sollte im Allgemeinen ein großes, tief verschachteltes Objekt gespeichert werden.
Die leitende Frage ist einfach:
Fragen Sie sich, ob ein Benutzer zu Recht erwarten würde, dass dieser Ansichtszustand geteilt, als Favorit gespeichert oder wiederhergestellt werden kann – falls ja, ist die URL wahrscheinlich der richtige Ort für diesen Zustand.
2. Hören Sie auf, Werte zu speichern, die Sie einfach berechnen können
Eine schnelle Lösung, um den React-Zustand zu reduzieren, besteht darin, aufzuhören, Werte zu speichern, die eigentlich aus anderen Werten abgeleitet werden.
Nehmen Sie dieses Beispiel:
const [items, setItems] = useState([]);
const [itemCount, setItemCount] = useState(0);
Und an anderer Stelle im Komponenten-Code:
setItems(newItems);
setItemCount(newItems.length);
Jetzt haben Sie zwei separate Zustandsvariablen, die denselben zugrundeliegenden Sachverhalt beschreiben.
Falls items aktualisiert wird, aber itemCount nicht mitgeführt wird, zeigt Ihre Benutzeroberfläche ungenaue Informationen an.
Ihre Implementierung hat ein Synchronisierungsproblem verursacht, das überhaupt nicht notwendig war.
Eine sauberere Version sieht so aus:
const [items, setItems] = useState([]);
const itemCount = items.length;
Es gibt nun eine einzige Quelle der Wahrheit.
Die Anzahl wird jedes Mal berechnet, wenn das Komponente gerendert wird.
Dieselbe Logik gilt auch beim Filtern von Sammlungen.
Anstatt Folgendes zu schreiben:
const [items, setItems] = useState([]);
const [activeItems, setActiveItems] = useState([]);
kann man in der Regel einfach Folgendes schreiben:
const activeItems = items.filter(item => item.active);
Oder:
const totalPrice = items.reduce(
(total, item) => total + item.price,
0
);
Oder:
const hasErrors = fields.some(field => field.error);
Oder:
const completedCount = todos.filter(todo => todo.completed).length;
Die richtige Frage lautet nicht:
">Kann ich das technisch gesehen im State speichern?"
Sicher, das ist möglich.
Die eigentliche Frage sollte lauten:
">Hat dieser Wert sein eigenes, separates Lebenszyklus?"
Falls nicht, sollte er wahrscheinlich nicht als State verwendet werden.
3. Hören Sie auf, jedes Formfeld mit dem Zustand zu verbinden
Eine weitere Angewohnheit, die man ablegen sollte, ist es, jedes Formfeld als eigenständige Zustandsvariable zu behandeln.
Eine kleine Form, die auf diese Weise erstellt wird, könnte so aussehen:
function ContactForm() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault(); submitData({
firstName,
lastName,
email,
message
});
}; return (
<form onSubmit={handleSubmit}>
<input
value={firstName}
onChange={e => setFirstName(e.target.value)}
/> <input
value={lastName}
onChange={e => setLastName(e.target.value)}
/> <input
value={email}
onChange={e => setEmail(e.target.value)}
/> <textarea
value={message}
onChange={e => setMessage(e.target.value)}
/> <button type="submit">Send</button>
</form>
);
}
Technisch gesehen ist hier nichts falsch.
Kontrollierte Eingabefelder sind sinnvoll, wenn Ihre Anwendung tatsächlich darauf reagieren muss, während der Benutzer eintippt. Einige Situationen, in denen das wichtig ist:
- Überprüfung der Gültigkeit eines Feldes während des Eingabevorgangs
- Angebot von Vorschlägen im Stil der Autocomplete-Funktion
- Auf der Stelle Neuformatierung von Text
- Zählung der verbleibenden Zeichen
- Anzeigen oder Verbergen anderer Felder je nach Eingabe
- Bewertung der Stärke eines Passworts
- Verknüpfung des Verhaltens eines Feldes mit dem Wert eines anderen Feldes
Aber oft hat Ihre Anwendung keinen Grund, sich dafür zu interessieren, was der Benutzer eingegeben hat, bis er auf Senden klickt.
In solchen Fällen ist es nicht notwendig, dass React jeden eingegebenen Zeichen verfolgt.
Der Browser übernimmt diese Aufgabe bereits.
Lassen Sie den Browser das erledigen
Native HTML-Formulare verwalten Eingabewerte bereits seit Jahrzehnten.
Man kann sich auf dieses eingebaute Verhalten mit FormData stützen:
function ContactForm() {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget); const payload = {
firstName: formData.get('firstName') as string,
lastName: formData.get('lastName') as string,
email: formData.get('email') as string,
message: formData.get('message') as string,
}; submitData(payload);
}; return (
<form onSubmit={handleSubmit}>
<input name="firstName" required />
<input name="lastName" required />
<input name="email" type="email" required />
<textarea name="message" required /> <button type="submit">Send</button>
</form>
);
}
Mit diesem Muster benötigt React keinen separaten Zustandsplatz mehr für jedes Feld.
Der Browser speichert, was der Benutzer eingegeben hat.
React liest die Werte einfach, sobald das Formular abgesendet wird.
Das ist besonders vorteilhaft bei großen Formularen.
Stellen Sie sich einen Onboarding-Prozess mit 30 Feldern vor.
Möchten Sie wirklich 30 separate Zustandsvariablen nur dafür, um das Eingetippte zu speichern?
Wahrscheinlich nicht.
Kontrollierte und unkontrollierte Eingaben können nebeneinander existieren
Das bedeutet keineswegs, dass kontrollierte Komponenten ein Fehler sind.
Sie sind es nicht.
Es gibt viele Situationen, in denen die Steuerung der Eingaben genau richtig ist.
Es geht einfach darum, die Annahme aufzugeben, dass React standardmäßig den Wert jedes Feldes kontrollieren muss.
Falls Ihre Anwendung die Daten erst bei der Übermittlung benötigt, reicht es oft aus, wenn das native Formular sie speichert.
Diese Unterscheidung kann große Formulare erheblich vereinfachen.
4. Warum boolesche Flags versagen
Ein weiteres häufig auftretendes Problem zeigt sich ständig in realen Codebasen.
Betrachten Sie einen typischen API-Aufruf.
Ein gängiges Muster besteht darin, dies mit drei separaten Boole-Werten zu verfolgen:
const [isLoading, setIsLoading] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);
Zu Beginn sind alle drei falsch:
loading = false
success = false
error = false
Sobald die Anfrage startet:
loading = true
Sobald sie erfolgreich abgeschlossen ist:
loading = false
success = true
Auf den ersten Blick scheint das vernünftig zu sein.
Aber nehmen wir an, eine Fehlerbehandlungsstruktur schafft es nicht, isLoading zurückzusetzen.
Dann könnte es zu folgendem kommen:
loading = true
error = true
Oder ein ungenauer Wiederholungsmechanismus könnte dazu führen, dass:
success = true
error = true
Zu diesem Zeitpunkt befindet sich Ihre Komponente in einem logisch unsinnigen Zustand.
Soll sie die Erfolgsnachricht anzeigen?
Soll sie den Fehlzustand anzeigen?
Soll sie weiterhin einen Ladeindikator zeigen?
Technisch gesehen könnten alle drei Booleschen Werte gleichzeitig eine „Ja“-Antwort rechtfertigen.
Genau diese Mehrdeutigkeit ist der Fehler.
Betrachten Sie es stattdessen als endliche Zustandsmaschine
Ein saubererer Ansatz:
type RequestStatus =
| 'idle'
| 'loading'
| 'success'
| 'error';
const [status, setStatus] =
useState<RequestStatus>('idle');
Nun sind alle gültigen Zustände explizit und gegenseitig ausschließend.
Die Anfrage kann nur einer der folgenden Zustände sein:
idle
loading
success
error
Es ist nicht möglich, versehentlich sowohl „Laden“ als auch „Erfolgreich“ darzustellen.
Die Darstellung wird dadurch erheblich einfacher verständlich:
if (status === 'loading') {
return <Spinner />;
}
if (status === 'error') {
return <ErrorMessage />;
}if (status === 'success') {
return <SuccessBanner />;
}return <DefaultForm />;
Das ist nicht nur ein nettes Extra von TypeScript.
Es spiegelt eine allgemeinere Gestaltungsregel wider:
Wenn zwei Zustandswerte niemals unabhängig voneinander wahr sein können, sollten sie nicht als zwei separate Variablen modelliert werden.
5. Vermeiden Sie die Verwendung von Effects, um den Zustand synchron zu halten
Auch hier führen zusätzliche Zustandsvariablen zu überflüssigem, vermeidbarem Code.
Nehmen wir diese Konfiguration:
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
In Kombination mit:
useEffect(() => {
setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
Was Sie hier erstellt haben, ist ein Synchronisierungsmechanismus für einen Wert, der ursprünglich überhaupt keinen eigenen Zustand benötigte.
Die einfachere Lösung:
const fullName = `${firstName} ${lastName}`;
Der Änderung mag wenig erscheinen, doch die architektonischen Auswirkungen sind erheblich.
Die ursprüngliche Version benötigt:
- Drei separate Zustandsvariablen
Die überarbeitete Version benötigt nur:
- Zwei echte Werte
- Einen aus ihnen abgeleiteten Wert
- Kein Effekt
- Keine Synchronisierungslogik, die aufrechterhalten werden muss
Eine gute Gewohnheit bei der Überprüfung von React-Komponenten ist es, Folgendes zu fragen:
"Warum muss dieser Wert außerhalb der Render-Berechnung existieren?"
Falls die ehrliche Antwort lautet "um mit etwas anderem synchron zu bleiben," ist das in der Regel ein Zeichen dafür, dass der Wert überhaupt kein State sein sollte.
6. Drei Fragen, bevor man State hinzufügt
Vor dem Schreiben dieser Zeile:
const [value, setValue] = useState(...);
prüfen Sie zunächst drei Dinge durch.
Frage 1: Kann dies während des Renderings berechnet werden?
Falls die zur Erzeugung dieses Wertes benötigten Eingaben bereits vorhanden sind, gibt es keinen Grund, eine Duplikate zu speichern.
Anstelle von:
const [itemCount, setItemCount] = useState(0);
schreiben Sie:
const itemCount = items.length;
Anstelle von:
const [hasErrors, setHasErrors] = useState(false);
schreiben Sie:
const hasErrors = fields.some(field => field.error);
Vermeiden Sie das Speichern von Zuständen für alles, was direkt berechnet werden kann.
Frage 2: Soll dieser Zustand nach einem Seitenlade überleben?
Falls ja, prüfen Sie, ob die URL der richtige Träger für diesen Zustand ist.
Typische Kandidaten:
- Suchbegriffe
- Paginierungsstatus
- Aktive Filter
- Sortierordnung
- Ausgewählte Tabellen
- Datumintervalle
Fragen Sie sich: Sollte ein Link zu dieser Seite geteilt werden, landet der Empfänger dann auf exakt derselben Ansicht?
Falls ja, ist die URL in der Regel der bessere Ort für diese Daten.
Frage 3: Kann der Browser diesen Zustand bis zum Absenden des Formulars speichern?
Falls der Wert eines Feldes nur zum Zeitpunkt der Übermittlung relevant ist, reicht möglicherweise eine unkontrollierte Eingabe aus.
Anstelle von:
const [email, setEmail] = useState('');
könnte man einfach nur brauchen:
<input name="email" type="email" />
und es später mit folgendem lesen:
const formData = new FormData(e.currentTarget);
Lassen Sie den Browser das verwalten, wofür er bereits konzipiert ist.
7. Was wirklich in den React-State gehört
Nichts davon ist ein Argument dafür, den Component-State vollständig abzuschaffen.
Ein Versuch, ihn komplett zu entfernen, wäre überkorrigiert.
Viele Werte eignen sich durchaus dazu, im State gespeichert zu werden.
Zum Beispiel:
const [isOpen, setIsOpen] = useState(false);
Ein temporärer Dropdown oder ein Modal-Fenster muss keineswegs in der URL widergespiegelt werden.
Interaktiver State
const [activeStep, setActiveStep] = useState(1);
Wenn ein Wert eine Interaktion antreibt, die sich vollständig innerhalb eines Components abspielt, ist es oft richtig, ihn im lokalen State zu speichern.
Werte, die unabhängig voneinander ändern
Falls etwas tatsächlich seinen eigenen Lebenszyklus hat und es nicht möglich ist, diesen anhand bereits vorhandener Daten zu berechnen, dann ist der Zustand der geeignete Ort dafür.
Optimistische Benutzeroberfläche
Oft muss man etwas auf dem Bildschirm anzeigen, bevor der Server tatsächlich bestätigt hat, dass es stattgefunden hat.
Auch das ist eine legitime Verwendung des Zustands.
Es geht nicht darum, dieser Regel zu folgen:
"Verwende unbedingt weniger
useState, egal was passiert."
Sondern vielmehr darum:
">Sicherstellen, dass jeder Zustandswert den richtigen Besitzer hat."
Mannchmal ist der richtige Besitzer React.
Mannchmal ist es die URL.
Mannchmal ist es das DOM.
Mannchmal ist es der Server.
Mannchmal ist es eine spezielle Schicht zur Datenerfassung oder Zustandsverwaltung.
Genau diese Entscheidung bezüglich des Eigentums prägt tatsächlich Ihre Architektur.
8. Konzentrieren Sie sich darauf, wer den Zustand besitzt – nicht nur darauf, wie viel davon Sie haben
Eine gut konstruierte React-Anwendung wird nicht dadurch definiert, dass sie nur eine geringe Anzahl an Zustandsvariablen besitzt.
Was wirklich wichtig ist, ist dass jedem Zustück ein klarer, richtiger Eigentümer zugeordnet wird.
Nehmen wir ein Dashboard, das Folgendes überwacht:
Search query
Filters
Pagination
Sort order
Form values
Server data
Loading status
Selected row
Modal visibility
Derived totals
Es ist verlockend, all diese Elemente ohne weiteres Nachdenken in useState zu packen.
Doch diese Werte erfüllen nicht dieselbe Funktion, daher sollten sie auch nicht denselben „Aufbewahrungsort“ teilen.
Ein nützlicheres Denkmodell teilt sie wie folgt auf:
URL
- Suche
- Filter
- Paginierung
- Sortierordnung
- Teilbare Tabellen
DOM
- Formularwerte, die nur nach der Abgabe des Formulars relevant sind
Server / Datenlayer
- Kundendaten
- API-Antworten
- Gespeicherte Serverdaten
Ableitete Werte
- Zählungen
- Gesamtwerte
- Gefilterte Listen
- Zusammenfassungen der Validierung
React-State
- Sichtbarkeit von Modalen
- Vorübergehende Auswahlmöglichkeiten
- Zustand von Animationen
- Kurzlebige Interaktionen
Sobald der State auf diese Weise verteilt wird, muss das Komponenten-Objekt weitaus weniger synchronisieren.
Diese Reduzierung ist es, die den eigentlichen Vorteil bringt.
9. Doppelte State sind die Quelle der Fehler von morgen
Der riskanteste State in einer Anwendung ist nicht unbedingt der komplexeste State.
Oft ist es der State, der gleichzeitig an mehreren Stellen vorhanden ist.
Stellen Sie sich vor, Sie haben:
const [products, setProducts] = useState([]);
const [filteredProducts, setFilteredProducts] = useState([]);
Nun sind Sie dafür verantwortlich zu gewährleisten, dass filteredProducts stets mit dem Inhalt von products übereinstimmt.
Fügen Sie einen weiteren Filter hinzu, und es ergibt sich:
const [filteredProducts, setFilteredProducts] = useState([]);
const [searchResults, setSearchResults] = useState([]);
Jetzt gibt es eine zusätzliche Beziehung, die Sie manuell konsistent halten müssen.
Schneller oder später aktualisiert jemand die zugrunde liegenden Daten, vergisst aber, eine der davon abhängigen abgeleiteten Variablen zu aktualisieren.
Die Schnittstelle gerät leise aus dem Einklang mit der Realität.
Irgendwann meldet ein Benutzer etwas in der Art:
"Die Anzahl gibt 42 Produkte an, aber nur 39 werden auf dem Bildschirm angezeigt."
Diese Diskrepanz ist in der Regel kein Fehler in der Filterlogik selbst.
Sie ist ein Symptom dafür, dass die App für dieselben Informationen mehr als eine Quelle der Wahrheit hat.
Falls Sie den Wert stattdessen ableiten:
const filteredProducts = products.filter(...);
Man beseitigt eine ganze Klasse von Fehlern, bevor sie auftreten können.
Deshalb geht es bei der Reduzierung des Zustands nicht nur um die Render-Leistung.
Es geht vielmehr darum, die Anzahl der Elemente zu verringern, die aus dem Gleichklang geraten können.
10. Die Kosten des Zustands gehen über erneute Renders hinaus
Wenn Menschen über Zustand sprechen, dreht sich die Diskussion meist um Leistung: „Wird das einen weiteren Render auslösen?“
Das ist eine berechtigte Sorge, aber es ist nicht der teuerste Aspekt.
Der eigentliche Kostenfaktor ist die Komplexität.
Jede zusätzliche Zustandsvariable bedeutet etwas, das das Team im Kopf behalten muss.
Dann fügt man unweigerlich einen Setter-Aufruf hinzu.
setSomething(...)
Dann wahrscheinlich ein useEffect, um darauf zu reagieren.
useEffect(...)
Dann ein Abhängigkeitsarray, das die von ihm überwachten Werte auflistet.
[firstValue, secondValue]
Dann eine Art Reset-Logik, um den Wert wieder auf seinen ursprünglichen Wert zurückzusetzen.
setSomething(initialValue)
Dann ein Randfall, in dem dieser Zustand mit einer Navigationssänderung übereinstimmen muss.
Dann ein Fehlerbericht, weil der Zustand nach einer bestimmten Änderung nicht bereinigt wurde.
Stück für Stück verwandelt sich die Codebasis in ein Netzwerk aus Synchronisierungslogiken.
Das ist erheblich schwieriger zu verstehen als eine Komponente, deren Daten sauber aus einer Handvoll gut definierter Quellen stammen.
Die wahre Fähigkeit in React liegt nicht im Verwalten mehrerer Zustände
Sondern darin, zu verstehen wohin jeder Zustandselement gehört.
Ein weniger erfahrener Entwickler, der sich eine Benutzeroberfläche ansieht, könnte fragen:
"Welchen Zustand benötigt das?"
Ein besseres architektonisches Verständnis führt zu einer anderen Frage:
"Welche dieser Werte müssen überhaupt im Zustand gespeichert werden?"
Die präziseste Form der Frage lautet:
"Wer ist der rechtmäßige Eigentümer jedes Zustandselements?"
Wenn man seine Denkweise auf diese Weise verändert, kann sich auch der Ansatz bei der Entwicklung von React-Anwendungen grundlegend ändern.
Die URL kann für den Navigationszustand zuständig sein.
Der Browser kann für den Formzustand zuständig sein.
Der Server kann für vom Server gehaltene Daten zuständig sein.
Ableitete Werte können weiterhin als Ableitungen bleiben, anstatt dupliziert zu werden.
TypeScript-Union-Typen können Zustände modellieren, die sich gegenseitig ausschließen.
Und React selbst kann sich auf den kurzlebigen, interaktiven Zustand konzentrieren, der tatsächlich zur UI-Schicht gehört.
Diese Aufgabenteilung führt zu einer viel saubereren Architektur.
Zusammenfassung
Mehr Zustände an sich sind kein Erfolg.
Eine größere Anzahl an Zustandsvariablen macht eine Anwendung nicht leistungsfähiger.
In der Regel bedeutet das nur mehr Synchronisierungsarbeit, mehr Abhängigkeiten, die verfolgt werden müssen, sowie mehr Möglichkeiten, dass die Daten inkonsistent werden.
Bevor Sie wieder auf useState zurückgreifen, beantworten Sie zunächst diese Fragen:
- Kann dieser Wert während der Darstellung berechnet werden? Wenn ja, ermitteln Sie ihn lieber, anstatt ihn separat zu speichern.
- Muss er nach einem Seitenaufruf erhalten bleiben oder per Link geteilt werden? Wenn ja, fügen Sie ihn zu den Suchparametern der URL hinzu.
- Muss dieses Formfeld bei jeder Tastenbetätigung verfolgt werden? Wenn nicht, reicht möglicherweise eine unkontrollierte Eingabe in Kombination mit
FormDataaus.
Ziel war niemals, die Anzahl der useState-Aufrufe insgesamt zu minimieren.
Ziel ist vielmehr sicherzustellen, dass so wenige Werte wie möglich für die Bestimmung der Richtigkeit Ihrer Anwendung verantwortlich sind.
Denn jeder unnötige Zustand bedeutet eine weitere Sache, die Ihre Anwendung synchronisieren muss.
Jeder Schritt der Synchronisierung, den Sie vermeiden, ist ein Fehler, den Sie nicht beheben müssen.