Strona główna / Artykuły

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.

Wydawca: REACTAPP.TOP4 min czytania1017 słów

Źródła: Vibe Coding Can Build the Store. Can You Design It?

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
  • Zustand — wspólna baza danych klientów z drobnymi subskrypcjami poza strukturą drzewa
  • Redux Toolkit — wyraźne zdarzenia i przewidywalne przejścia w ustrukturyzowanej architekturze
  • 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