Startseite / Artikel

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.

Verlag: REACTAPP.TOP17 Min. Lesezeit3931 Wörter

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
  • Bildschirme, auf die niemand verlinken oder zur späteren Nutzung speichern kann
  • Kombinationen von Flags, die einen UI-Zustand erzeugen, der eigentlich nie existieren sollte
  • 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
  • Ein Effekt, um sie auszurichten
  • Eine implizite Synchronisierungsbeziehung zwischen den Werten
  • Ein zusätzlicher Renderzyklus, der durch den Effekt ausgelöst wird
  • Ein Abhängigkeitsarray, das genau sein muss
  • 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:

    1. Kann dieser Wert während der Darstellung berechnet werden? Wenn ja, ermitteln Sie ihn lieber, anstatt ihn separat zu speichern.
    2. Muss er nach einem Seitenaufruf erhalten bleiben oder per Link geteilt werden? Wenn ja, fügen Sie ihn zu den Suchparametern der URL hinzu.
    3. Muss dieses Formfeld bei jeder Tastenbetätigung verfolgt werden? Wenn nicht, reicht möglicherweise eine unkontrollierte Eingabe in Kombination mit FormData aus.
  • Beschreiben mehrere boolesche Flags tatsächlich denselben zugrundeliegenden Lebenszyklus? Fassen Sie sie zu einem einzigen Statuswert oder einer Zustandsmaschine zusammen.
  • Wird dieser Zustand nur erstellt, um ihn mit einem anderen Zustand in Einklang zu bringen? Wenn dem so ist, speichern Sie vermutlich etwas, das Sie genauso gut direkt ableiten könnten.
  • Hat dieser Wert wirklich einen eigenen Lebenszyklus? Wenn nicht, benötigt er vermutlich überhaupt keinen Zustand.
  • 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.

    Verwandte Artikel

  • Warum React useEffect Callbacks niemals Asynfunktionen sein sollten — Erfahren Sie, warum das Zurückgeben einer Asynfunktion aus useEffect den Cleanup-Vertrag von React bricht, und sehen Sie vier korrekte Muster zur sicheren Handhabung von asynchrone Logik.
  • Authentifizierung vs Autorisierung: Wo jede in Ihrem Code hingehört — Erfahren Sie, wie sich Authentifizierung und Autorisierung in Bezug auf Implementierung, HTTP-Statuscodes und Systemarchitektur unterscheiden, um häufige Sicherheitsfehler zu vermeiden.