W centrum dowodzenia statku kosmicznego, prosty panel z jednym przyciskiem to za mało. Gdy systemy pokładowe stają się coraz bardziej złożone - tarcze, silniki, systemy nawigacji, alarmy - potrzebujemy bardziej uporządkowanego sposobu zarządzania stanem. Hook
useReducer to jak centralny komputer pokładowy, który przyjmuje rozkazy (akcje) i na ich podstawie aktualizuje stan wszystkich systemów według ściśle określonych reguł.useReducer to alternatywa dla useState, zaprojektowana do obsługi bardziej złożonej logiki stanu. Zamiast bezpośrednio ustawiać nową wartość, wysyłasz akcję (rozkaz), a specjalna funkcja zwana reducerem decyduje, jak stan powinien się zmienić.1const [state, dispatch] = useReducer(reducer, initialState);Gdzie:
state - aktualny stan (dane systemów statku)dispatch - funkcja do wysyłania akcji (wydawania rozkazów)reducer - funkcja, która na podstawie bieżącego stanu i akcji zwraca nowy staninitialState - początkowa wartość stanuAnalogia kosmiczna: Pomyśl o
dispatch jako o panelu sterowania, na którym wciskasz przyciski (akcje). Komputer pokładowy (reducer) odbiera rozkaz i na podstawie aktualnego stanu statku oblicza nowy stan. Nigdy nie modyfikujesz systemów ręcznie - zawsze przez komputer!Reducer to czysta funkcja, która przyjmuje dwa argumenty: aktualny stan i akcję. Na ich podstawie zwraca nowy obiekt stanu. Najczęściej używamy instrukcji
switch do obsługi różnych typów akcji:1function spaceshipReducer(state, action) {
2 switch (action.type) {
3 case 'ACTIVATE_SHIELDS':
4 return { ...state, shields: 100, status: 'defended' };
5
6 case 'FIRE_ENGINES':
7 return { ...state, speed: state.speed + action.payload, fuel: state.fuel - 10 };
8
9 case 'EMERGENCY_STOP':
10 return { ...state, speed: 0, status: 'stopped' };
11
12 default:
13 return state;
14 }
15}Kluczowe zasady reducera:
default - zwracaj niezmieniony stan dla nieznanych akcjiAkcja to zwykły obiekt JavaScript z polem
type opisującym rodzaj operacji. Opcjonalnie możesz dodać pole payload z dodatkowymi danymi:1// Prosta akcja - tylko typ
2dispatch({ type: 'ACTIVATE_SHIELDS' });
3
4// Akcja z danymi (payload)
5dispatch({ type: 'FIRE_ENGINES', payload: 50 });
6
7// Akcja ze złożonym payloadem
8dispatch({
9 type: 'ADD_CREW_MEMBER',
10 payload: { name: 'Kapitan Nova', role: 'pilot' }
11});Zobaczmy, jak
useReducer sprawdza się w zarządzaniu złożonym stanem panelu kontrolnego:1import { useReducer } from 'react';
2
3const initialShipState = {
4 fuel: 100,
5 shields: 0,
6 speed: 0,
7 status: 'docked',
8 log: []
9};
10
11function shipReducer(state, action) {
12 switch (action.type) {
13 case 'LAUNCH':
14 if (state.fuel < 20) return state;
15 return {
16 ...state,
17 status: 'flying',
18 fuel: state.fuel - 20,
19 speed: 50,
20 log: [...state.log, 'Statek wystartował']
21 };
22
23 case 'BOOST':
24 if (state.status !== 'flying' || state.fuel < 10) return state;
25 return {
26 ...state,
27 speed: state.speed + action.payload,
28 fuel: state.fuel - 10,
29 log: [...state.log, `Przyspieszono o ${action.payload} km/s`]
30 };
31
32 case 'TOGGLE_SHIELDS':
33 return {
34 ...state,
35 shields: state.shields > 0 ? 0 : 100,
36 log: [...state.log, state.shields > 0 ? 'Tarcze wyłączone' : 'Tarcze aktywowane']
37 };
38
39 case 'DOCK':
40 return {
41 ...state,
42 status: 'docked',
43 speed: 0,
44 log: [...state.log, 'Statek zadokował']
45 };
46
47 case 'REFUEL':
48 return {
49 ...state,
50 fuel: 100,
51 log: [...state.log, 'Zatankowano paliwo']
52 };
53
54 default:
55 return state;
56 }
57}
58
59function ShipControlPanel() {
60 const [ship, dispatch] = useReducer(shipReducer, initialShipState);
61
62 return (
63 <div className="control-panel">
64 <h2>Panel sterowania</h2>
65 <p>Status: {ship.status}</p>
66 <p>Paliwo: {ship.fuel}%</p>
67 <p>Tarcze: {ship.shields}%</p>
68 <p>Prędkość: {ship.speed} km/s</p>
69
70 <button onClick={() => dispatch({ type: 'LAUNCH' })}
71 disabled={ship.status === 'flying'}>
72 Start
73 </button>
74 <button onClick={() => dispatch({ type: 'BOOST', payload: 25 })}
75 disabled={ship.status !== 'flying'}>
76 Przyspiesz (+25)
77 </button>
78 <button onClick={() => dispatch({ type: 'TOGGLE_SHIELDS' })}>
79 {ship.shields > 0 ? 'Wyłącz tarcze' : 'Włącz tarcze'}
80 </button>
81 <button onClick={() => dispatch({ type: 'DOCK' })}
82 disabled={ship.status === 'docked'}>
83 Dokuj
84 </button>
85 <button onClick={() => dispatch({ type: 'REFUEL' })}
86 disabled={ship.status === 'flying'}>
87 Zatankuj
88 </button>
89
90 <div className="ship-log">
91 <h3>Dziennik pokładowy:</h3>
92 <ul>
93 {ship.log.map((entry, i) => (
94 <li key={i}>{entry}</li>
95 ))}
96 </ul>
97 </div>
98 </div>
99 );
100}Zwróć uwagę, jak reducer centralizuje całą logikę aktualizacji stanu. Komponent nie musi wiedzieć, jak dokładnie zmienia się stan - wystarczy, że wyśle odpowiednią akcję. To ogromna zaleta przy rozbudowanych interfejsach.
Oba hooki służą do zarządzania stanem, ale mają różne zastosowania:
Wybierz
gdy:useState
Wybierz
gdy:useReducer
1// useState - wystarczy dla prostych przypadków
2const [isOpen, setIsOpen] = useState(false);
3const [count, setCount] = useState(0);
4
5// useReducer - lepszy dla złożonego stanu
6const [shipState, dispatch] = useReducer(shipReducer, {
7 fuel: 100,
8 shields: 0,
9 speed: 0,
10 status: 'docked',
11 crew: [],
12 alerts: [],
13 systems: { engine: 'online', navigation: 'online', weapons: 'offline' }
14});Instrukcja
switch to standardowy wzorzec w reducerach. Oto kilka dobrych praktyk:1// Definiuj stałe dla typów akcji - unikasz literówek
2const ACTIONS = {
3 LAUNCH: 'LAUNCH',
4 DOCK: 'DOCK',
5 BOOST: 'BOOST',
6 TAKE_DAMAGE: 'TAKE_DAMAGE',
7 REPAIR: 'REPAIR'
8};
9
10function shipReducer(state, action) {
11 switch (action.type) {
12 case ACTIONS.LAUNCH:
13 return { ...state, status: 'flying', speed: 50 };
14
15 case ACTIONS.DOCK:
16 return { ...state, status: 'docked', speed: 0 };
17
18 case ACTIONS.BOOST:
19 return {
20 ...state,
21 speed: Math.min(state.speed + action.payload, 300)
22 };
23
24 case ACTIONS.TAKE_DAMAGE: {
25 const newShields = Math.max(0, state.shields - action.payload);
26 return {
27 ...state,
28 shields: newShields,
29 status: newShields === 0 ? 'critical' : state.status
30 };
31 }
32
33 case ACTIONS.REPAIR:
34 return { ...state, shields: 100, status: 'docked' };
35
36 default:
37 throw new Error(`Nieznana akcja: ${action.type}`);
38 }
39}
40
41// Użycie ze stałymi
42dispatch({ type: ACTIONS.BOOST, payload: 50 });
43dispatch({ type: ACTIONS.TAKE_DAMAGE, payload: 30 });Zauważ, że w
TAKE_DAMAGE użyliśmy bloku {} wewnątrz case - to pozwala na deklarację zmiennych lokalnych (newShields) bez konfliktów z innymi case'ami. Dodatkowo, zamiast default: return state, rzucamy błąd - to pomaga wyłapać literówki w nazwach akcji podczas programowania.Dla wygody i czytelności możesz tworzyć funkcje, które generują obiekty akcji:
1// Kreatory akcji
2const launch = () => ({ type: 'LAUNCH' });
3const boost = (amount) => ({ type: 'BOOST', payload: amount });
4const takeDamage = (amount) => ({ type: 'TAKE_DAMAGE', payload: amount });
5const addCrewMember = (name, role) => ({
6 type: 'ADD_CREW_MEMBER',
7 payload: { name, role }
8});
9
10// Użycie - czytelniejsze niż ręczne tworzenie obiektów
11dispatch(launch());
12dispatch(boost(50));
13dispatch(takeDamage(25));
14dispatch(addCrewMember('Nova', 'pilot'));Kreatory akcji zmniejszają ryzyko literówek i centralizują logikę tworzenia akcji w jednym miejscu.
useReducer to potężne narzędzie do zarządzania złożonym stanem w React:const [state, dispatch] = useReducer(reducer, initialState)(state, action) => newState z wzorcem switch-casedispatch({ type: 'ACTION', payload: data }){} w case, rzucanie błędów w defaultTak jak centralny komputer pokładowy statku kosmicznego koordynuje pracę wszystkich systemów na podstawie rozkazów z mostka kapitańskiego, tak
useReducer centralizuje logikę stanu, czyniąc ją przewidywalną i łatwą do zarządzania.