Staatsmanagement: Das Atom vs. der Monolith
Redux ist (größtenteils) tot. Der Kontext ist eine Falle. Ein technischer tiefer Einblick in das moderne React State Management: Zustand, Jotai und TanStack Query.
Das Herzstück jeder React-Anwendung ist der „Staat“. Wenn die Benutzeroberfläche eine Funktion des Zustands ist (€UI = f(state)€), dann ist die Verwaltung dieses Zustands die wichtigste technische Entscheidung, die Sie treffen.
Jahrelang hatten wir Redux. Es war ausführlich, zentralisiert und unveränderlich. Es hat funktioniert, aber Sie haben mehr Textbausteine als Code geschrieben. Dann hatten wir Kontext. Es war eingebaut und einfach. Wir beeilten uns alle, es zu benutzen. Dann begannen unsere Apps langsamer zu werden. Die Eingabe einer Texteingabe führte dazu, dass die Seitenleiste neu gerendert wurde. Wir haben festgestellt: Context ist ein Dependency-Injection-Tool, kein State-Management-Tool.
Im Jahr 2025 ist das Ökosystem ausgereift. Wir kategorisieren den Zustand nun in drei verschiedene Ebenen, jede mit einem speziellen Werkzeug.
Warum Maison Code dies bespricht
Wir haben festgestellt, dass die App eines Kunden aufgrund eines einzelnen Kontextanbieters 4.000 Mal pro Sekunde neu gerendert wurde. Wir haben sie in eine Tri-Layer State Architecture verschoben:
- Serverstatus: TanStack-Abfrage (für Caching und Hydratation).
- Globaler Client-Status: Zustand (für Warenkorb und Theme).
- Lokaler Status: Signale oder useState (für Formulareingaben). Dadurch wurde die Blockierungszeit des Hauptthreads um 600 ms reduziert und die INP-Werte auf <50 ms verbessert. Wir glauben, dass die Zustandsverwaltung die häufigste Ursache für Leistungseinbußen in React-Apps ist.
Die Theorie: Die drei Schichten
1. Serverstatus (Der Cache)
Daten, die auf einem Remote-Server gespeichert sind. Sie „besitzen“ es nicht; man „leiht“ es sich. Es kann langweilig werden.
- Beispiele: Benutzerprofil, Produktliste, Bestellungen.
- Tool: TanStack Query (React Query) oder SWR.
- Anti-Pattern: API-Daten manuell in Redux/Zustand einfügen. Am Ende erfinden Sie Caching, Deduplizierung und Ladezustände neu.
2. Client-Status (die Benutzeroberfläche)
Daten, die nur im Browser vorhanden sind und von allen Komponenten gemeinsam genutzt werden.
- Beispiele: Ist das Modal offen? Was ist das aktuelle Thema? Was ist im Warenkorb?
- Tool: Zustand (für globale Stores) oder Jotai (für atomare Updates).
3. Lokaler Zustand (die Komponente)
Auf eine einzelne Komponente oder ihre direkten untergeordneten Komponenten isolierte Daten.
- Beispiele: Ist das Dropdown geöffnet? Welchen Wert hat dieses spezielle Eingabefeld?
- Tool:
useState/useReducer.
Das Problem mit dem Kontext
Warum nicht einfach „createContext“ für alles verwenden? Aufgrund der grobkörnigen Reaktivität.
„tsx const DataContext = createContext();
Funktion Provider({ children }) { const [name, setName] = useState(“Alex”); const [theme, setTheme] = useState(“Dark”);
// Diese Objektreferenz ändert sich bei JEDEM Update const value = { name, theme, setName, setTheme };
return <DataContext.Provider value={value}>{children}</DataContext.Provider>; } „
Wenn setName("John") aufgerufen wird:
- Das „Wert“-Objekt wird neu erstellt.
- Jede Komponente, die „useContext(DataContext)“ aufruft, wird neu gerendert.
- Sogar die „ThemeToggler“-Komponente wird neu gerendert, obwohl sich „theme“ nicht geändert hat!
Sie können dies mit „useMemo“ und Aufteilen von Kontexten („NameContext“, „ThemeContext“) optimieren, aber das führt zur „Context Hell“ (Pyramid of Doom).
Die Lösung 1: Zustand (The Monolith Replacement)
Zustand ist ungefähr „Redux ohne das Boilerplate“. Es verwendet eine Store-Architektur, löst aber das Problem des erneuten Renderns mit Selektoren.
„tsx import { create } from ‘zustand’;
const useStore = create((set) => ({ Bären: 0, Fisch: 0, raiseBears: () => set((state) => ({ bears: state.bears + 1 })), raiseFish: () => set((state) => ({ fish: state.fish + 1 })), }));
Funktion BearCounter() { // SELECTOR: Diese Komponente beobachtet NUR „Bears“ const bears = useStore((state) => state.bears); return
{bears} Bears
; } „Wenn „increaseFish()“ aufgerufen wird, rendert „BearCounter“ nicht erneut. Zustand vergleicht den Rückgabewert des Selektors („state.bears“). Wenn sich nichts geändert hat, wird die Aktualisierung übersprungen.
Middleware-Leistung
Zustand hat größtenteils eine Größe von 1 KB, aber leistungsstarke Middleware. Persist: Speichert den Status automatisch in „localStorage“. Immer: Ermöglicht veränderbare Syntax („state.bears++“) in Updates.
„tsx import { persist } from ‘zustand/middleware’;
const useCartStart = create( bestehen bleiben( (set) => ({ Elemente: [], addItem: (id) => set((state) => ({ items: […state.items, id] })), }), { name: ‘cart-storage’ } // Geben Sie localStorage ein ) ); „
Zugriff auf externe Komponenten
Entscheidend ist, dass auf Zustandsspeicher außerhalb von React zugegriffen werden kann (z. B. in Dienstprogrammfunktionen oder API-Interceptoren).
„Javascript // api.ts import { useAuthStore } from ’./authStore’;
const token = useAuthStore.getState().token; // Nicht-Hook-Zugriff fetch(‘/api’, { headers: { Authorization: token } }); „
Die Lösung 2: Jotai (Der atomare Ansatz)
Jotai (inspiriert von Recoil) verfolgt einen anderen Ansatz. Anstelle eines großen Speichers haben Sie Tausende winziger Atome. Der Staat ist von unten nach oben aufgebaut.
„tsx import { atom, useAtom } from ‘jotai’;
// Atome deklarieren const priceAtom = atom(10); const amountAtom = atom(2);
// Berechnetes (abgeleitetes) Atom const totalAtom = atom((get) => get(priceAtom) * get(quantityAtom));
Funktion Cart() { const [total] = useAtom(totalAtom); return
Anwendungsfall: Hochgradig interaktive Apps wie Tabellenkalkulationen, Diagrammeditoren oder Dashboards, bei denen die Abhängigkeiten komplex sind. Wenn Sie „priceAtom“ aktualisieren, werden nur die Komponenten neu gerendert, die auf „price“ oder „total“ hören. Es ist chirurgische Präzision.
Serverstatus: Umgang mit Async
Wir befürworten dringend TanStack Query. Es behandelt die schwierigen Teile des asynchronen Status:
- Stale-While-Revalidate: Alte Daten anzeigen, während neue abgerufen werden.
- Fokus-Neuabruf: Daten aktualisieren, wenn der Benutzer mit der Tabulatortaste zurück zum Fenster wechselt.
- Deduplizierung: Wenn 10 Komponenten nach einem Benutzerprofil fragen, wird nur 1 Netzwerkanfrage gestellt.
„tsx const { data, isLoading } = useQuery({ queryKey: [‘Benutzer’, ID], queryFn: () => fetchUser(id), staleTime: 1000 * 60, // Daten sind 1 Minute lang aktuell }); „
Eine Vermischung mit Zustand ist üblich. Zustand enthält den „Filter“-Status. React Query enthält die von diesem Filter abgeleiteten „Listen“-Daten.
Zusammenfassung: Die Entscheidungsmatrix
| Anforderung | Empfehlung | Warum? |
|---|---|---|
| API-Daten | TanStack-Abfrage | Caching, Deduplizierung, Ladestatus integriert. |
| Globale Benutzeroberfläche | Zustand | Einfache, kleine Selektoren verhindern ein erneutes Rendern. |
| Komplexer Graph | Jotai | Die Verfolgung atomarer Abhängigkeiten ist leistungsstark. |
| Formularstatus | Hook-Formular reagieren | Unkontrollierte Eingaben erzielen eine bessere Leistung. |
10. Signale: Die Zukunft? (Präakt/Fest)
React rendert Komponenten neu.
Signale (übernommen von Preact, Solid, Vue, Angular) aktualisieren das DOM direkt.
const count = signal(0);
<div>{count}</div>
Wenn „count.value++“ auftritt, wird die Komponentenfunktion NICHT erneut ausgeführt.
Nur der Textknoten im DOM wird aktualisiert.
Das ist O(1)-Komplexität.
React untersucht dies mit „React Compiler“ (Forget), aber Signale sind ein grundsätzlich effizienteres Grundelement für feinkörnige Reaktivität.
Wir überwachen diesen Raum genau. Für leistungsstarke Dashboards (Kryptohandel) verwenden wir manchmal Preact + Signals anstelle von React.
11. Persistenter Zustand (Local-First)
Die Bedingung „persistent“ ist einfach. Aber wie sieht es mit validen Offline-First-Daten aus? Wir bewegen uns in Richtung Local-First Software (LoFi). Tools wie RxDB oder Replicache. Der „State Manager“ ist eigentlich eine lokale Datenbank, die im Hintergrund synchronisiert wird. Dadurch wird der Server als „sekundäre“ Quelle der Wahrheit behandelt. Der Client ist primär. Durch diese Architektur fühlen sich Apps sofort an (0 ms Latenz).
13. Das Beobachtermuster (MobX)
Vor Signals gab es MobX. Es verwendet „observable“ Objekte und „Observer“ (Komponenten). Es fühlt sich an wie Magie. Sie mutieren ein Objekt „user.name = „John““ und die Komponente wird aktualisiert. Wir empfehlen MobX nicht für neue Projekte, da es zu viel Komplexität verbirgt (Magic Proxies). Es ist bekanntermaßen schwierig, die Frage „Warum wurde das gerendert?“ zu debuggen. Für sehr spezifische, datenintensive Apps (wie eine Tabellenkalkulation) ist MobX jedoch aufgrund seiner feinkörnigen Abhängigkeitsverfolgung schneller als Redux.
14. Redux Toolkit (RTK): Die Modernisierung
Wenn Sie Redux (Enterprise Legacy) *verwenden müssen, verwenden Sie RTK. Es verhält sich wie Zustand.
- Keine „switch“-Anweisungen.
- Integriertes Immer (veränderliche Syntax).
- Integrierte RTK-Abfrage (ähnlich der TanStack-Abfrage). Durch die Migration vom alten Redux zu RTK wird die Codegröße um 60 % reduziert. Aber wenn Sie neu anfangen: Zustand ist 1 KB, RTK ist 40 KB. Wählen Sie mit Bedacht.
15. Ehrung der Gefallenen: Rückstoß
Wir müssen Recoil (Meta) erwähnen. Es erfand das „Atom“-Konzept für React. Aber es wird derzeit nicht gepflegt (Meta wurde auf andere interne Tools verschoben). Jotai hob die Fackel auf. Wenn Sie über eine Recoil-Codebasis verfügen, migrieren Sie zu Jotai. Die API ist zu 90 % ähnlich („useRecoilState“ -> „useAtom“). Starten Sie keine neuen Projekte mit Recoil.
16. Fazit
Bei der Staatsverwaltung geht es nicht mehr darum, „die eine Bibliothek zu finden, die sie alle beherrscht“. Es geht darum, spezielle Werkzeuge zu komponieren. Bei Maison Code verwenden wir standardmäßig den Stack Zustand + React Query. Es ist robust, leistungsstark und entwicklerfreundlich.
Legacy-Redux umgestalten?
Liegt Ihre Anwendung unter Boilerplate und langsamen Reduzierern?