Akcja to zwykły obiekt opisujący co się stało; reducer to czysta funkcja, która przyjmuje bieżący stan i akcję oraz zwraca kolejny stan. Razem implementują przewidywalne przejścia stanów.
Akcje — opisują zdarzenia
{ : , : { : , : } }
{ : , : }
Akcja to zwykły obiekt opisujący co się stało; reducer to czysta funkcja, która przyjmuje bieżący stan i akcję oraz zwraca kolejny stan. Razem implementują przewidywalne przejścia stanów.
{ : , : { : , : } }
{ : , : }
Akcje nic nie robią — są deklaratywnymi opisami zdarzeń. Nazwanie ich faktami w czasie przeszłym ("added", "toggled") odzwierciedla fakt, że rejestrują, co się stało.
function todosReducer(state = [], action) {
switch (action.type) {
case "todos/added":
return [...state, action.payload]; // new array
case "todos/toggled":
return state.map(t => // new array, new object for the changed one
t.id === action.payload ? { ...t, done: !t.done } : t
);
default:
return state; // unknown action → unchanged
}
}
Sygnatura reducera to (state, action) => newState. Musi zwrócić nowy obiekt/tablicę stanu (niezmiennie), nigdy nie modyfikować wejścia.
Czysta funkcja: (1) zwraca to samo wyjście dla tego samego wejścia oraz (2) nie ma efektów ubocznych. Reducery muszą przestrzegać obu reguł:
// ❌ NOT pure — breaks everything
function badReducer(state, action) {
state.value++; // ❌ mutates input (breaks change detection, time-travel)
fetch("/api/log"); // ❌ side effect (non-deterministic, untestable)
return { value: Math.random() }; // ❌ non-deterministic output
}
Czystość umożliwia charakterystyczne cechy Redux:
✓ Predictability — same state + action ALWAYS gives the same result
✓ Time-travel / replay — you can re-run actions to reproduce any state
✓ Testability — just call reducer(state, action) and assert; no mocks needed
✓ Change detection — returning new references lets the UI detect changes
Efekty uboczne (wywołania API, logowanie, losowość) należą do middleware (thunks/sagas) lub effects — nigdy do reducerów.
Model akcji/reducera jest sercem zarządzania stanem w stylu Redux i przyczyną, dla której jest tak debugowalny i testowalny.
Wymóg czystości nie jest arbitralny: to to, co umożliwia debugowanie poprzez podróż w czasie, powtórkę akcji i trywialne testy jednostkowe, i dlatego efekty uboczne muszą znajdować się gdzie indziej.
Ten napędzany czystymi funkcjami, niezmiennie aktualizujący się wzorzec pojawia się w Redux, NgRx, useReducer i poza tym — zrozumienie go (i dyscypliny utrzymywania reducerów czystymi) jest fundamentalne dla przewidywalnego stanu.