Kontekst, stan lub Redux Toolkit: wybierz w zależności od złożoności przejścia
Wybieraj narzędzia do zarządzania stanem w React według złożoności przejść, a nie wielkości aplikacji: Context rozprzestrzenia zależności drzewa, Zustand usprawnia subskrypcje, Redux Toolkit modeluje przejścia.
Sztuczna inteligencja sprawiła, że jedna z czynności związanych z frontendem stała się niemal bezpłatna: tworzenie kodu. Prosząc o magazyn Zustand, fragment Redux lub Context wraz z useReducer, model wygeneruje coś, co da się skompilować. Składnia przestała być trudnym elementem – trudniejsze jest puste szkicowanie architektury oraz decyzja o tym, w jaki sposób stan powinien przepływać przez produkt.
To jest prawdziwy problem zarządzania stanem. Context API, Zustand i Redux Toolkit podchodzą do niego z różnych kierunków. Chodzi tu nie o błahe szczegóły API, lecz o to, co kryje się pod spodem.
1. Context API: drzewo React stanowi granicę
Context odpowiada na konkretne pytanie: jak wartość może dotrzeć do głęboko położonych komponentów bez konieczności przechodzenia przez każdą warstwę za pomocą propów?
Dokumentacja React opisuje useContext jako mechanizm służący do odczytu i obserwowania kontekstu. Komponent pobiera wartość od najbliższego dostępного providera znajdującego się powyżej niego. Gdy wartość tego providera ulega zmianie, dane są aktualizowane.
Najważniejszą kwestią nie jest sam wywołanie hooka — to relacja w drzewie.
Provider definiuje środowisko; konsument odczytuje z niego dane.
Kontekst istnieje w hierarchii React.
Kontekst sam w sobie nie zarządza stanem
Powszechnym błędnym przekonaniem jest to, że Context to biblioteka do zarządzania stanem. Aktualizacje zazwyczaj pochodzą od useState, reducerów lub niestandardowych hooków. Kontekst głównie przekazuje wartości wytwarzane przez te hooki.
Ogromna pułapka kontekstu
Ludzie mówią: „Kontekst powoduje niepotrzebne ponowne renderowanie”. Ten slogan jest zbyt dosadny. React porównuje poprzednią i następną wartość value dostawcy za pomocą funkcji Object.is. Gdy wartość się zmienia, aktualizowany jest każdy odczytujący element; granica memo nie blokuje nowego kontekstu.
Rozważmy zestaw niepowiązanych ze sobą pól:
<AppContext
value={{
user,
cart,
orders,
notifications
}}
>
Głębszy problem ma charakter architektoniczny: istnieje jedna szeroka granica zależności dla domen, które nie należą do siebie.
Zamiast:
AppContext
wolimy:
AuthContext
ThemeContext
FeatureFlagContext
Każdy kontekst ma wtedy jasną, semantyczną funkcję. Zasada projektowa: kontekst powinien reprezentować sensowną granicę zależności. Kontekst nie jest „słaby w skali” – często problemem są ogromne konteksty.
2. Opuść drzewo: Zustand
Kontekst jest powiązany z drzewem komponentów. Zustand stawia inne pytanie: jak wspólny stan klienta może istnieć poza tym drzewem przy precyzyjnych subskrypcjach?
Magazyn może przechowywać stan oraz funkcje, które go zmieniają:
const useCartStore = create((set) => ({
items: [],
addItem: (item) =>
set((state) => ({
items: [...state.items, item]
})),
removeItem: (id) =>
set((state) => ({
items: state.items.filter(
item => item.id !== id
)
}))
}));
Komponenty wybierają odpowiednie fragmenty stanu:
const items = useCartStore(
state => state.items
);
Zwycięstwem jest tu granica subskrypcji. Komponent nie prosi o cały magazyn; prosi tylko o tę część, która go interesuje. Selektory sprawiają, że ten kontrakt jest jawny.
Dlaczego Redux nadal się pojawia
Jeśli Zustand jest tak prosty, to dlaczego Redux Toolkit? Ponieważ problem może przestać dotyczyć „gdzie umieścić tę wartość?” i stać się „jak rozumieć wszystko, co może ją zmienić?”
Załóżmy nakładające się domeny:
Checkout
Payment
Inventory
Orders
Refunds
Permissions
Przepływ pracy może wyglądać w ten sposób:
Checkout Started
↓
Payment Started
↓
Payment Succeeded
↓
Inventory Reserved
↓
Order Created
Jeśli rezerwacja towaru nie udaje się po pomyślnym dokonaniu płatności, problemem nie jest już przechowywanie — to kwestia przejścia stanu. Właśnie w tym aspekcie struktura Redux okazuje się przydatna.
3. Redux jako wyraźne przejścia
Korzystnym ujęciem nie jest stwierdzenie „Redux to globalna baza danych”. Traktuj go jako wyraźny system do opisywania zdarzeń i obliczania przewidywalnych kolejnych stanów:
nextState =
reducer(previousState, action)
Przepływ w Redux
W porównaniu z prostym set(...) to wygląda na skomplikowane:
set(...)
Każdy krok ma swoje zadanie:
- Akcja — co się wydarzyło?
- Reducer — jak powinien ulec zmianie stan?
- Baza danych — jaki jest obecny stan?
- Selektor — czego potrzebuje ten komponent?
To właśnie ta jasność jest kluczową cechą. Gdy zasady biznesowe się plączą, wiedza o dlaczego nastąpiła zmiana stanu może być ważniejsza niż oszczędzenie dziesięciu linijek kodu.
Redux Toolkit to nowoczesny Redux
Klasyczny Redux wymagał ręcznego definiowania typów działań oraz ich twórców. Toolkit eliminuje to za pomocą funkcji createSlice:
const cartSlice = createSlice({
name: "cart",
initialState: {
items: []
},
reducers: {
addItem(state, action) {
state.items.push(action.payload);
}
}
});
Związane ze sobą stany i przejścia pozostają razem; generowane są twórcy działań. Immer umożliwia składnię przypominającą mutacje, jednocześnie zapewniając niezmienne aktualizacje. Nowoczesny Redux to mniej „pisania szablonów” i więcej „utrzymywania uporządkowanego modelu przejść bez historycznych formalności”.
Middleware i operacje asynchroniczne
Middleware znajduje się pomiędzy funkcją dispatch a redukcją:
To właśnie w tym obszarze znajdują się funkcje logowania, analizy danych, asynchroniczne procesy robocze oraz reakcje na określone działania. Toolkit dostarcza narzędzia takie jak createAsyncThunk oraz middleware do obsługi słuchaczy. Zustand pozostawia organizację asynchroniczną otwartą; Toolkit oferuje bardziej ugruntowane wzorce.
Który wybrać?
Unikaj heurystyki opartej na rozmiarze aplikacji:
- Aplikacja mała → Context
- Aplikacja średnia → Zustand
- Aplikacja duża → Redux
Liczba ekranów to kiepski wskaźnik złożoności stanu. Aplikacja CRUD z setką ekranów może być prostsza od aplikacji do obsługi płatności i uprawnień składającej się z dziesięciu ekranów.
Lepiej skupić się na relacjach i przejściach:
Model myślowy do zachowania
Nie próbuj zapamiętywać trzech logo. Pamiętaj o trzech podejściach:
- Context — rozpraszanie zależności w drzewie React
Nieodpowiednią pierwszą pytaniem jest „Która biblioteka jest najlepsza?”. Lepiej zapytać: jaki rodzaj złożoności wymaga zarządzania? Wybierz abstrakcję, która ułatwia rozumienie tej złożoności — a nie tę oznaczoną jako „nowoczesna”.
Literatura pokrewna
- Context vs Redux Toolkit vs Zustand dla stanu w React — Porównaj Context, Redux Toolkit i Zustand w kontekście portalu wideo, a następnie połącz sklepy klienckie z TanStack Query do zarządzania stanem serwera.
- Zustand w praktyce: małe sklepy, precyzyjni selektory, aplikacja do śledzenia roślin — Zastąp metody typu prop-drilling oraz skomplikowane rozwiązania Redux funkcjami create(), selektorami, operacjami asynchronicznymi, middleware persist/devtools, elementami typu slices oraz aplikacją do śledzenia roślin.