mit RTK Query und createSlice
Redux Toolkit hat klassisches Redux mit seinen endlosen Switch Statements und Action Type Konstanten weitgehend abgelöst. Mit createSlice, RTK Query und Entity Adapter lässt sich globaler State in React Anwendungen mit deutlich weniger Code, dafür mit mehr Typsicherheit und eingebautem Server State Caching abbilden.
Inhaltsverzeichnis
- 1. Warum Redux Toolkit und nicht klassisches Redux
- 2. createSlice: Reducer Logik ohne Boilerplate
- 3. RTK Query: Server State ohne manuelle Caching Logik
- 4. Selektoren mit createSelector und Memoization
- 5. Entity Adapter: Normalisierte Daten verwalten
- 6. Async Flows: createAsyncThunk versus RTK Query
- 7. TypeScript Integration: Typsichere Slices und Hooks
- 8. Middleware und DevTools: Debugging und Erweiterbarkeit
- 9. Redux Toolkit im Vergleich zu anderen Ansätzen
- 10. Zusammenfassung
- 11. FAQ
1. Warum Redux Toolkit und nicht klassisches Redux
Klassisches Redux verlangte für jeden noch so kleinen State drei Action Type Konstanten, mindestens einen Action Creator und einen Switch Statement Reducer mit manuell geschriebener Immutability Logik. Redux Toolkit wurde von den Redux Maintainern selbst entwickelt, um genau dieses Boilerplate zu eliminieren, ohne die Grundidee von Redux, einem einzigen vorhersagbaren Store mit unidirektionalem Datenfluss, aufzugeben. Seit einigen Jahren gilt Redux Toolkit als der offiziell empfohlene Weg, Redux überhaupt zu verwenden, klassisches Redux ohne Toolkit gilt inzwischen als Legacy Pattern.
Der Kern von Redux Toolkit besteht aus configureStore, createSlice und createAsyncThunk, ergänzt durch das separate Paket RTK Query für Server State. Unter der Haube nutzt Redux Toolkit die Bibliothek Immer, wodurch Reducer Funktionen so aussehen dürfen, als würden sie den State direkt mutieren, obwohl im Hintergrund weiterhin ein neuer, unveränderlicher State erzeugt wird. Für Teams, die bereits mit Redux arbeiten und auf React 19 migrieren, ist Redux Toolkit meist der pragmatischste Weg, weil der Store selbst erhalten bleibt und nur die Schreibweise der Slices modernisiert wird.
Ein oft übersehener Vorteil von Redux Toolkit: die DevTools Integration ist bereits vorkonfiguriert, ebenso sinnvolle Middleware Defaults wie die Serializable Check und die Immutability Check, die in der Entwicklung automatisch Verstöße gegen die Redux Grundregeln erkennen. Wer diese Warnungen ernst nimmt, verhindert subtile Bugs, die in klassischem Redux oft erst in Produktion auffallen.
2. createSlice: Reducer Logik ohne Boilerplate
createSlice ist die zentrale Funktion von Redux Toolkit und fasst Action Types, Action Creators und Reducer in einer einzigen Deklaration zusammen. Statt für jede Aktion einen eigenen String zu definieren, generiert createSlice die Action Types automatisch aus dem Slice Namen und den Reducer Funktionsnamen. Innerhalb der Reducer Funktionen erlaubt Immer die direkte Zuweisung an Objekteigenschaften und Array Push Operationen, was den Code deutlich näher an normalen JavaScript Funktionen hält als das reine Redux Pattern mit Spread Operatoren.
Ein zweiter wichtiger Baustein ist prepare, mit dem sich die Payload einer Aktion vor dem eigentlichen Reducer transformieren lässt, etwa um eine eindeutige ID zu generieren, bevor ein Eintrag in den State geschrieben wird. Für asynchrone Abläufe, die nicht über RTK Query laufen, lassen sich Extra Reducer über extraReducers an einen Slice anhängen, sodass ein Slice auf die Pending, Fulfilled und Rejected Zustände eines separat definierten Thunks reagieren kann, ohne dass der Thunk selbst im Slice definiert sein muss.
// features/cartSlice.js — createSlice replaces action types, creators and reducer boilerplate
import { createSlice } from '@reduxjs/toolkit';
const cartSlice = createSlice({
name: 'cart',
initialState: { items: [], total: 0 },
reducers: {
itemAdded: (state, action) => {
// Immer lets us "mutate" state directly, a new immutable state is created behind the scenes
const existing = state.items.find((item) => item.id === action.payload.id);
if (existing) {
existing.quantity += 1;
} else {
state.items.push({ ...action.payload, quantity: 1 });
}
state.total = state.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
},
itemRemoved: (state, action) => {
state.items = state.items.filter((item) => item.id !== action.payload);
state.total = state.items.reduce((sum, item) => sum + item.price * item.quantity, 0);
},
cartCleared: (state) => {
state.items = [];
state.total = 0;
},
},
});
export const { itemAdded, itemRemoved, cartCleared } = cartSlice.actions;
export default cartSlice.reducer;
3. RTK Query: Server State ohne manuelle Caching Logik
RTK Query ist der wohl größte Sprung, den Redux Toolkit gegenüber klassischem Redux gemacht hat. Statt Thunks für jeden API Aufruf manuell zu schreiben, inklusive Loading Flags, Error Handling und Caching, definiert man mit createApi eine deklarative API Slice, aus der automatisch typisierte React Hooks generiert werden. RTK Query übernimmt Request Deduplizierung, Cache Invalidation über Tags und automatisches Refetching, sobald sich abhängige Daten ändern, alles ohne zusätzliche Bibliothek.
Das Tag basierte Invalidation System ist dabei das mächtigste Konzept: Jede Query kann Tags bereitstellen, jede Mutation kann dieselben Tags invalidieren, und RTK Query triggert automatisch ein Refetch aller betroffenen Queries. Für Teams, die bereits Redux im Einsatz haben, ersetzt RTK Query damit häufig separate Data Fetching Bibliotheken, weil der Server State im selben Store landet wie der Client State und mit denselben DevTools inspiziert werden kann.
// features/apiSlice.js — RTK Query replaces manual thunks and caching logic
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
export const productsApi = createApi({
reducerPath: 'productsApi',
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['Product'],
endpoints: (builder) => ({
getProducts: builder.query({
query: () => 'products',
providesTags: (result) =>
result
? [...result.map(({ id }) => ({ type: 'Product', id })), { type: 'Product', id: 'LIST' }]
: [{ type: 'Product', id: 'LIST' }],
}),
updateProduct: builder.mutation({
query: ({ id, ...patch }) => ({ url: `products/${id}`, method: 'PATCH', body: patch }),
// Invalidating this tag triggers an automatic refetch of getProducts
invalidatesTags: (result, error, { id }) => [{ type: 'Product', id }],
}),
}),
});
export const { useGetProductsQuery, useUpdateProductMutation } = productsApi;
4. Selektoren mit createSelector und Memoization
Ohne Memoization berechnet jeder useSelector Aufruf abgeleitete Daten bei jedem Render neu, selbst wenn sich die relevanten Eingabedaten nicht geändert haben. createSelector aus Redux Toolkit löst das über Memoization: Der Selektor merkt sich das letzte Ergebnis und berechnet nur neu, wenn sich mindestens eine der Input Selektoren geänderten Wert liefert. Bei komplexen Ableitungen wie gefilterten und sortierten Listen ist das der Unterschied zwischen einem Rerender in Mikrosekunden und einem spürbaren Ruckler bei größeren Datenmengen.
Ein häufiger Fehler ist, Selektoren mit neuen Objektreferenzen als Rückgabewert zu schreiben, etwa state => ({ items: state.cart.items }). Dieses Objekt Literal erzeugt bei jedem Aufruf eine neue Referenz, wodurch React Redux jeden Render als Änderung interpretiert und die Memoization von createSelector wirkungslos wird. Die Lösung ist, entweder primitive Werte zurückzugeben oder das gesamte abgeleitete Objekt selbst über createSelector zu memoizieren, sodass nur bei echter Änderung eine neue Referenz entsteht.
// features/cartSelectors.js — createSelector memoizes derived state
import { createSelector } from '@reduxjs/toolkit';
const selectCartItems = (state) => state.cart.items;
const selectCategoryFilter = (state) => state.filters.category;
// Recomputes only when selectCartItems or selectCategoryFilter actually change
export const selectFilteredCartTotal = createSelector(
[selectCartItems, selectCategoryFilter],
(items, category) =>
items
.filter((item) => !category || item.category === category)
.reduce((sum, item) => sum + item.price * item.quantity, 0)
);
5. Entity Adapter: Normalisierte Daten verwalten
Listen von Entitäten, etwa Produkte, Benutzer oder Bestellungen, werden in vielen Redux Stores naiv als Array gespeichert. Das führt bei Updates einzelner Einträge zu linearem Suchaufwand und bei ID basiertem Zugriff zu unnötig komplexem Code. createEntityAdapter normalisiert solche Collections automatisch in die Form { ids: [], entities: {} }, wodurch der Zugriff auf ein einzelnes Element per ID zu einer konstanten Objektlookup Operation wird statt einer linearen Suche durch ein Array.
Entity Adapter generiert zusätzlich vorgefertigte Reducer Funktionen wie addOne, updateOne, removeMany und upsertMany, die sich direkt in die reducers Definition eines createSlice Aufrufs einhängen lassen. In Kombination mit RTK Query lässt sich der Adapter sogar direkt in der transformResponse Funktion einer Query einsetzen, sodass Serverdaten bereits normalisiert im Cache landen, bevor eine Komponente sie über einen generierten Selektor abruft.
// features/productsSlice.js — Entity Adapter normalizes list state
import { createSlice, createEntityAdapter } from '@reduxjs/toolkit';
const productsAdapter = createEntityAdapter({
sortComparer: (a, b) => a.name.localeCompare(b.name),
});
const productsSlice = createSlice({
name: 'products',
initialState: productsAdapter.getInitialState({ status: 'idle' }),
reducers: {
productUpserted: productsAdapter.upsertOne,
productsReceived: productsAdapter.upsertMany,
productRemoved: productsAdapter.removeOne,
},
});
// Generated selectors give O(1) lookups instead of array scans
export const {
selectAll: selectAllProducts,
selectById: selectProductById,
selectIds: selectProductIds,
} = productsAdapter.getSelectors((state) => state.products);
export const { productUpserted, productsReceived, productRemoved } = productsSlice.actions;
export default productsSlice.reducer;
6. Async Flows: createAsyncThunk versus RTK Query
Bevor RTK Query existierte, war createAsyncThunk der Standardweg für asynchrone Logik in Redux Toolkit, und für Fälle jenseits von reinem Data Fetching bleibt es relevant, etwa für mehrstufige Workflows, Datei Uploads mit Fortschrittsanzeige oder Aktionen, die mehrere API Aufrufe orchestrieren. createAsyncThunk generiert automatisch drei Action Types, Pending, Fulfilled und Rejected, auf die ein Slice über extraReducers reagieren kann, um Ladezustand, Ergebnis und Fehler im State abzubilden.
Für reines Server State Management, also das Laden, Cachen und Aktualisieren von Daten von einer REST oder GraphQL Schnittstelle, ist RTK Query heute die deutlich bessere Wahl, weil es Deduplizierung, Cache Invalidation und Polling ohne zusätzlichen Code mitbringt. Die Faustregel in modernen Redux Toolkit Projekten lautet: RTK Query für alles, was aus einer Datenquelle kommt und synchronisiert werden muss, createAsyncThunk für Business Logik, die zwar asynchron ist, aber keinen klassischen Cache benötigt.
7. TypeScript Integration: Typsichere Slices und Hooks
Redux Toolkit wurde von Grund auf mit TypeScript im Kopf entworfen, und createSlice leitet die meisten Typen automatisch aus dem initialState Objekt ab, sodass in den Reducer Funktionen kaum manuelle Typannotationen nötig sind. Für Actions mit Payload reicht PayloadAction<Type> als Typ für den Action Parameter, wodurch der Compiler bereits beim Schreiben des Reducers auf falsche Payload Formen hinweist, lange bevor ein Test fehlschlägt.
Für den Store selbst empfiehlt sich, RootState und AppDispatch aus configureStore abzuleiten und typisierte Versionen von useSelector und useDispatch zu exportieren. Dieses Pattern verhindert, dass in jeder Komponente der State Typ manuell angegeben werden muss, und stellt sicher, dass Refactorings im Store sofort als Compile Fehler in allen betroffenen Komponenten sichtbar werden, statt erst zur Laufzeit als undefined Fehler aufzutauchen.
// app/store.ts — typed store setup for React and Redux Toolkit
import { configureStore } from '@reduxjs/toolkit';
import cartReducer from '../features/cartSlice';
import { productsApi } from '../features/apiSlice';
export const store = configureStore({
reducer: {
cart: cartReducer,
[productsApi.reducerPath]: productsApi.reducer,
},
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(productsApi.middleware),
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
// hooks.ts — typed hooks, use these everywhere instead of plain useSelector/useDispatch
import { useDispatch, useSelector, type TypedUseSelectorHook } from 'react-redux';
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
8. Middleware und DevTools: Debugging und Erweiterbarkeit
configureStore aktiviert standardmäßig eine Reihe von Middleware Checks, die in der Entwicklung typische Redux Fehler frühzeitig sichtbar machen. Die Immutability Middleware erkennt versehentliche direkte State Mutationen außerhalb von Immer Kontexten, die Serializable Check warnt, wenn nicht serialisierbare Werte wie Klassen Instanzen oder Promises im State oder in Actions landen, was spätere Persistierung oder Time Travel Debugging unmöglich machen würde.
Für eigene Querschnittsanliegen, etwa zentrales Logging, Analytics Tracking oder das automatische Anhängen von Auth Tokens an bestimmte Actions, lässt sich eigene Middleware über getDefaultMiddleware().concat(customMiddleware) ergänzen. Die Redux DevTools Extension ist bei configureStore standardmäßig aktiviert und erlaubt Time Travel Debugging, das Zurückspulen einzelner Actions und das Exportieren kompletter Action Historien, was bei der Reproduktion schwer greifbarer Bugs enorm hilft.
9. Redux Toolkit im Vergleich zu anderen Ansätzen
Redux Toolkit konkurriert heute weniger mit klassischem Redux, das gilt inzwischen als überholt, sondern mit leichteren Bibliotheken wie Zustand oder Jotai sowie mit reinem Server State Tooling wie TanStack Query. Die folgende Tabelle ordnet Redux Toolkit entlang der Kriterien ein, die in der Praxis über die Wahl entscheiden.
| Kriterium | Redux Toolkit | Zustand | TanStack Query allein |
|---|---|---|---|
| Boilerplate pro Feature | Gering dank createSlice, aber Store Setup nötig | Minimal, ein Hook pro Store | Minimal, nur für Server State |
| Server State Caching | Eingebaut über RTK Query | Nicht eingebaut, externe Lösung nötig | Kernfunktion der Bibliothek |
| DevTools und Time Travel | Vollständig, produktionsreif | Rudimentär über Middleware | Eigenes, auf Queries fokussiertes Panel |
| Eignung bei großen Teams | Konventionen erzwingen Konsistenz | Erfordert eigene Konventionen | Gut, aber nur für Server State |
| Lernkurve | Höher, Store, Slices, Provider | Sehr niedrig | Niedrig für Data Fetching |
In der Praxis entscheidet meist die Teamgröße und die Menge an geteiltem Client State: Kleine Teams mit wenig komplexem Client State fahren oft besser mit Zustand plus TanStack Query, während große Teams mit vielen Slices von den erzwungenen Redux Toolkit Konventionen und den ausgereiften DevTools profitieren.
Mironsoft
React Architektur, State Management und moderne Frontend Infrastruktur
Redux Store der wächst statt zu bremsen?
Wir analysieren bestehende Redux Stores, migrieren Legacy Redux auf Redux Toolkit und führen RTK Query für euren Server State ein, mit sauberen Slices, typsicheren Selektoren und DevTools freundlicher Architektur.
Redux Migration
Legacy Redux auf createSlice und Entity Adapter migrieren, ohne Big Bang Rewrite
RTK Query Einführung
Manuelle Thunks durch deklarative API Slices mit Cache Invalidation ersetzen
TypeScript Hardening
Typisierte Hooks, RootState Ableitung und Compile Zeit Sicherheit im gesamten Store
10. Zusammenfassung
Redux Toolkit löst das klassische Redux Boilerplate Problem durch createSlice, das Action Types, Action Creators und Reducer in einer einzigen Deklaration bündelt und dank Immer direkte Mutationen im Reducer Code erlaubt. RTK Query übernimmt Server State Management vollständig, inklusive Deduplizierung, Tag basierter Cache Invalidation und automatischem Refetching, wodurch separate Data Fetching Bibliotheken oft überflüssig werden. Entity Adapter normalisiert Listen Daten für konstanten Zugriff per ID, createSelector memoiziert teure Ableitungen und verhindert unnötige Rerenders.
Für Teams mit großem, geteiltem Client State und dem Bedürfnis nach erzwungenen Konventionen bleibt Redux Toolkit die robusteste Wahl unter den State Management Optionen für React, gerade weil DevTools, Middleware und TypeScript Integration bereits produktionsreif mitgeliefert werden. Kleinere Anwendungen mit überschaubarem Client State fahren dagegen oft leichter mit schlankeren Alternativen wie Zustand in Kombination mit TanStack Query.
Redux Toolkit: Das Wichtigste auf einen Blick
createSlice
Bündelt Action Types, Action Creators und Reducer. Dank Immer sind direkte Mutationen im Reducer Code erlaubt und trotzdem immutable.
RTK Query
Deklarative API Slices mit generierten Hooks, Tag basierter Cache Invalidation und automatischem Refetching, ersetzt manuelle Thunks.
Entity Adapter & Selektoren
createEntityAdapter normalisiert Listen für O(1) Zugriff, createSelector memoiziert teure Ableitungen.
TypeScript & DevTools
Typisierte Hooks über RootState und AppDispatch, DevTools mit Time Travel Debugging von Beginn an aktiviert.