Von Redux zu Zustand migrieren: schlankeres State Management
AI generated
</>
{ }
React · State Management · Redux · Zustand
Von Redux zu Zustand migrieren
schlankeres State Management ohne Big Bang

Redux loest heute noch valide Probleme, produziert aber in vielen Projekten unnoetigen Boilerplate. Wer schrittweise von Redux zu Zustand migriert, ersetzt Reducer, Actions und Middleware Slice fuer Slice, ohne die App in einem riskanten Rutsch umzuschreiben.

18 Min. Lesezeit Store · Selectors · Middleware · Devtools Redux Toolkit · Zustand 5

1. Warum Teams von Redux zu Zustand migrieren

Redux war ueber Jahre die Standardantwort auf die Frage nach globalem State in React, aber viele Teams stellen fest, dass sie von Redux zu Zustand migrieren wollen, sobald der Boilerplate die eigentliche Fachlichkeit ueberdeckt. Ein einfacher Zaehler braucht in klassischem Redux eine Action, einen Action Creator, einen Reducer und einen Store Slice Eintrag. In Zustand ist derselbe Zaehler eine einzige Funktion mit einem Objekt, das State und Updater Funktionen kombiniert.

Ein zweiter Grund fuer die Migration ist die Bundle Groesse. Redux Toolkit inklusive React Redux Bindings wiegt deutlich mehr als Zustand, das ohne Provider, ohne Context und ohne zusaetzliche Bindings auskommt. Fuer Projekte, bei denen jedes Kilobyte im initialen Bundle zaehlt, ist das ein messbarer Vorteil, den Teams beim Vergleich der beiden Bibliotheken schnell erkennen.

Der dritte Grund ist die mentale Einstiegshuerde fuer neue Teammitglieder. Redux erfordert das Verstehen von Reducern, Immutability Patterns, Middleware und oft zusaetzlich Reselect fuer Selectors. Wer von Redux zu Zustand migriert, reduziert diese Konzepte auf ein einzelnes Store Objekt mit Funktionen, was die Einarbeitungszeit fuer neue Entwickler spuerbar verkuerzt.

2. Migrationsstrategie: Slice fuer Slice statt Big Bang

Eine Migration von Redux zu Zustand in einem einzigen Pull Request ist bei mittelgrossen bis grossen Anwendungen praktisch immer die falsche Entscheidung. Stattdessen migriert man Slice fuer Slice: ein fachlicher Bereich wie cart oder auth wird komplett auf Zustand umgestellt, waehrend der Rest der Anwendung weiterhin ueber Redux laeuft. Beide State Container koexistieren waehrend der gesamten Uebergangsphase im selben Baum.

Fuer die Reihenfolge gilt eine aehnliche Regel wie bei anderen Migrationen: Slices mit wenigen Abhaengigkeiten zu anderen Teilen des Stores zuerst, komplexe Slices mit vielen Cross Slice Selectors zuletzt. Ein Slice, der nur von einer einzigen Feature Komponente gelesen wird, laesst sich in Stunden migrieren. Ein Slice, der von zehn verschiedenen Komponenten quer durch die App gelesen wird, braucht deutlich mehr Planung und Testabdeckung, bevor er angefasst wird.

Wichtig ist, dass die oeffentliche Schnittstelle eines migrierten Slice zunaechst identisch bleibt. Komponenten, die bisher useSelector und useDispatch genutzt haben, sollten in einem Zwischenschritt auf einen eigenen Hook wie useCartStore umgestellt werden, der intern entweder Redux oder Zustand ansprechen kann. So bleibt die Migration fuer aufrufende Komponenten unsichtbar.

3. Den ersten Store Slice migrieren

Beim Uebertrag eines Redux Slice zu Zustand entfaellt die strikte Trennung zwischen Reducer und Action Creator. Zustand definiert State und die Funktionen, die ihn veraendern, in derselben Store Definition. Das reduziert die Anzahl der Dateien pro fachlichem Bereich haeufig von drei oder vier auf eine einzige.


// BEFORE: Redux Toolkit slice with actions, reducer and selectors
import { createSlice } from '@reduxjs/toolkit';

const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [] },
  reducers: {
    addItem: (state, action) => { state.items.push(action.payload); },
    removeItem: (state, action) => {
      state.items = state.items.filter(i => i.id !== action.payload);
    },
    clearCart: (state) => { state.items = []; },
  },
});

export const { addItem, removeItem, clearCart } = cartSlice.actions;
export const selectCartTotal = (state) =>
  state.cart.items.reduce((sum, i) => sum + i.price, 0);
export default cartSlice.reducer;

// AFTER: Zustand store combining state and actions in one place
import { create } from 'zustand';

export const useCartStore = create((set, get) => ({
  items: [],
  addItem: (item) => set((state) => ({ items: [...state.items, item] })),
  removeItem: (id) => set((state) => ({
    items: state.items.filter((i) => i.id !== id),
  })),
  clearCart: () => set({ items: [] }),
  cartTotal: () => get().items.reduce((sum, i) => sum + i.price, 0),
}));

Ein haeufiger Fehler bei dieser Migration: Entwickler versuchen, den Redux Reducer Stil mit einer riesigen switch Anweisung in Zustand nachzubilden, statt die direkten Update Funktionen zu nutzen, die Zustand anbietet. Wer von Redux zu Zustand migriert, sollte diese Gelegenheit nutzen, um den Code tatsaechlich zu vereinfachen, statt nur die Syntax auszutauschen und die alte Denkweise beizubehalten.

4. Selectors und Memoization ohne Reselect

In Redux wird Reselect haeufig eingesetzt, um teure abgeleitete Werte zu memoizen und unnoetige Re Renders zu verhindern. Zustand loest dieses Problem nativ ueber Selector Funktionen, die beim useCartStore(selector) Aufruf uebergeben werden. Solange der Selector nur ein primitives Feld liest, reicht die eingebaute Referenzgleichheit von Zustand vollstaendig aus, ganz ohne zusaetzliche Bibliothek.

Fuer komplexere abgeleitete Werte, die aus mehreren Feldern berechnet werden, empfiehlt sich weiterhin eine leichte Memoization mit useMemo im aufrufenden Component oder eine dedizierte Selector Funktion mit useShallow aus dem Zustand Paket, das flache Objektvergleiche fuer Selectors ermoeglicht, die mehrere Felder gleichzeitig zurueckgeben. Wer von Redux zu Zustand migriert und Reselect Selectors hatte, kann die reine Berechnungslogik meist unveraendert uebernehmen und nur den Memoization Mechanismus austauschen.

5. Async Actions: von Thunks zu einfachen Funktionen

Redux Thunks kapseln asynchrone Logik in Funktionen, die statt eines Action Objekts eine Funktion zurueckgeben, die dispatch und getState als Parameter erhaelt. Bei der Migration von Redux zu Zustand entfaellt dieser Umweg komplett, weil eine Zustand Action einfach eine normale asynchrone Funktion sein kann, die set direkt nach dem Await aufruft.


// BEFORE: Redux thunk for an async action
export const fetchProducts = () => async (dispatch, getState) => {
  dispatch({ type: 'products/loading' });
  try {
    const products = await api.getProducts();
    dispatch({ type: 'products/loaded', payload: products });
  } catch (error) {
    dispatch({ type: 'products/error', payload: error.message });
  }
};

// AFTER: Zustand store with the async logic inlined as a plain function
export const useProductStore = create((set) => ({
  products: [],
  isLoading: false,
  error: null,
  fetchProducts: async () => {
    set({ isLoading: true, error: null });
    try {
      const products = await api.getProducts();
      set({ products, isLoading: false });
    } catch (error) {
      set({ error: error.message, isLoading: false });
    }
  },
}));

6. Middleware und Persistenz nachbilden

Custom Redux Middleware fuer Logging, Analytics oder Persistenz muss bei der Migration nicht verloren gehen. Zustand bietet eigene Middleware Funktionen wie persist fuer LocalStorage Persistenz und subscribeWithSelector fuer gezielte Reaktionen auf State Aenderungen ausserhalb von React Komponenten, etwa fuer Analytics Events. Beide lassen sich wie Redux Middleware um den Store herum kombinieren.

Ein Team, das von Redux zu Zustand migriert und bisher redux-persist fuer die Synchronisation mit LocalStorage genutzt hat, ersetzt das durch die persist Middleware von Zustand, die deutlich weniger Konfiguration braucht und ohne separate Rehydration Logik in der App auskommt. Fuer sehr spezifische Middleware ohne direktes Zustand Aequivalent laesst sich fast immer eine eigene, wenige Zeilen lange Wrapper Funktion um die Store Definition schreiben.

7. Devtools und Time Travel Debugging behalten

Ein Argument, das gegen die Migration von Redux zu Zustand ins Feld gefuehrt wird, ist der Verlust der Redux Devtools mit Time Travel Debugging. Das stimmt so nicht mehr: Zustand bietet ueber die devtools Middleware dieselbe Browser Extension Integration, inklusive Action Namen, Diff Ansicht und der Moeglichkeit, zu einem frueheren State zurueckzuspringen.

Der einzige Unterschied ist, dass jede State Aenderungsfunktion in Zustand einen Namen fuer die Devtools bekommen sollte, etwa set(newState, false, 'cart/addItem'), damit die Devtools Anzeige aehnlich sprechend bleibt wie bei benannten Redux Actions. Teams, die von Redux zu Zustand migrieren, sollten diese Namenskonvention von Anfang an konsequent einhalten, um den Debugging Komfort nicht zu verlieren.

8. Redux und Zustand parallel betreiben

Waehrend der Uebergangsphase muessen beide State Container koexistieren koennen, ohne dass Komponenten wissen, welchen sie gerade lesen. Ein pragmatischer Ansatz ist ein Facade Hook pro fachlichem Bereich, der intern entweder useSelector oder den neuen Zustand Store anspricht, je nachdem, ob der Slice schon migriert wurde. Ein Feature Flag pro Slice macht sichtbar, welcher Teil der App bereits umgestellt ist.

Wichtig ist, dass beide Systeme wirklich unabhaengig bleiben und nicht versuchen, sich gegenseitig zu synchronisieren. Ein Redux Slice, der Werte aus einem Zustand Store liest, um sie in seinen eigenen State zu kopieren, erzeugt zwei Wahrheiten fuer denselben Wert und damit genau die Bugs, die eine saubere Migration eigentlich vermeiden soll.

9. Redux und Zustand im direkten Vergleich

Die folgende Tabelle fasst die wichtigsten Unterschiede zusammen, die bei der Entscheidung, von Redux zu Zustand zu migrieren, tatsaechlich den Ausschlag geben.

Aspekt Redux Toolkit Zustand Auswirkung
Boilerplate pro Feature Slice, Reducer, Selector Ein Store Objekt Deutlich weniger Dateien
Bundle Groesse Groesser mit React Redux Sehr klein, kein Provider Schnellere Initial Loads
Async Logik Thunks oder RTK Query Normale async Funktionen Weniger Konzepte lernen
Devtools Nativ eingebaut Ueber devtools Middleware Beide gleichwertig nutzbar
Grosse Teams, viele Slices Etablierte Konventionen Konventionen selbst aufsetzen Redux vorteilhaft bei sehr grossen Teams

Die Tabelle zeigt, dass die Migration von Redux zu Zustand nicht in jedem Fall die richtige Entscheidung ist. Sehr grosse Teams mit vielen Entwicklern profitieren von den etablierten, erzwungenen Konventionen von Redux, waehrend kleinere Teams und Projekte mit strengen Bundle Groessen Limits meist deutlich von Zustand profitieren.

Mironsoft

React State Management, Redux Migrationen und Architekturberatung

Redux Boilerplate stoert die Produktivitaet eures Teams?

Wir analysieren euren Redux Store, planen die Migration Slice fuer Slice und begleiten den Umstieg auf Zustand inklusive Middleware, Devtools und Testabdeckung.

Store Audit

Slice Analyse nach Abhaengigkeiten und Migrationsaufwand

Begleitete Migration

Koexistenz von Redux und Zustand ohne Regressionen

Middleware Uebertrag

Persistenz, Logging und Devtools Konventionen sauber uebernehmen

10. Zusammenfassung

Wer von Redux zu Zustand migrieren will, sollte niemals in einem Big Bang starten, sondern Slice fuer Slice vorgehen und mit fachlichen Bereichen beginnen, die wenige Abhaengigkeiten zu anderen Teilen des Stores haben. Reducer, Action Creator und Selector werden durch ein einziges Store Objekt ersetzt, das State und Update Funktionen kombiniert. Thunks weichen normalen asynchronen Funktionen, Middleware fuer Persistenz und Devtools bleibt ueber Zustand eigene Erweiterungen erhalten.

Der groesste Gewinn der Migration von Redux zu Zustand ist selten die reine Performance, sondern die drastisch reduzierte Menge an Boilerplate und die kuerzere Einarbeitungszeit fuer neue Teammitglieder. Fuer sehr grosse Teams mit strikten Konventionsanforderungen bleibt Redux dennoch in vielen Faellen die robustere Wahl, weshalb die Entscheidung fuer die Migration projektspezifisch getroffen werden sollte.

Von Redux zu Zustand migrieren: Das Wichtigste auf einen Blick

Migrationsreihenfolge

Slice fuer Slice, beginnend mit fachlichen Bereichen ohne viele Cross Slice Abhaengigkeiten.

Store Struktur

Reducer, Actions und Selectors werden zu einem einzigen Store Objekt mit set und get Funktionen.

Middleware

persist und devtools Middleware von Zustand decken die meisten Redux Middleware Anwendungsfaelle ab.

Koexistenz

Facade Hooks pro Slice halten die Migration fuer aufrufende Komponenten unsichtbar.

11. FAQ: Von Redux zu Zustand migrieren

1Lohnt sich die Migration fuer jedes Projekt?
Nicht zwingend, sehr grosse Teams profitieren von Redux Konventionen, kleinere Teams meist von Zustand.
2Koennen beide parallel laufen?
Ja, solange sie sich nicht gegenseitig synchronisieren und Facade Hooks die Migration verstecken.
3Was passiert mit Thunks?
Werden zu normalen asynchronen Funktionen, die set direkt nach dem Await aufrufen.
4Verliere ich die Devtools?
Nein, die devtools Middleware von Zustand bietet dieselbe Extension Integration inklusive Time Travel.
5Brauche ich ein Reselect Aequivalent?
Fuer einfache Selectors nicht, fuer komplexe hilft useShallow oder useMemo.
6Wie migriere ich Redux Persist?
Die persist Middleware von Zustand ersetzt redux-persist mit weniger Konfiguration.
7In welcher Reihenfolge migrieren?
Slices mit wenigen Abhaengigkeiten zuerst, stark verzahnte Slices zuletzt.
8Geeignet fuer grosse Teams?
Bedingt, eigene Konventionen fuer Store Struktur sollten dann festgelegt werden.
9Muss ich RTK Query auch ersetzen?
Nicht zwingend, kann parallel laufen oder durch TanStack Query ersetzt werden.
10Wie teste ich den neuen Store?
Direkt als Funktion ohne Provider in Unit Tests instanziierbar, ohne React zu rendern.