Dalej zagłębiamy się w bardzo istotny hook Reacta, jakim jest useReducer. Kontynuacja lekcji poprzednich, do dzieła!
Przykład znaleziony w internecie. Powyżej poziomu RFC mamy:
const initialState = {
balance: 0,
loan: 0,
isActive: false
};
Co to oznacza:
- Skoro powyżej poziomu RFC, to utworzenie tego obiektu będzie miało miejsce raz
- Skoro powyżej poziomu RFC, to nie możemy używać hooków typu useMemo ale z drugiej strony – zostanie utworzony raz
- Jako obiekt initialState będzie dostępny dla wszystkich RFC zdefiniowanych w tym samym pliku JS
- Jako obiekt initialState będzie wspóldzielony przez wszystkie instancje tego samego RFC zdefiniowanego w tym pliku JS
- Jako const, ale obiekt referencyjny, RFC będą mogły mutować ten obiekt (tego nie będziemy robić)
- Jeżeli zostanie przekazany jako initialState do dowolnego hooka (useState, useReducer) to te problemy nam znikają – każdy komponent na mount utworzy własny initial state w oparciu o ten obiekt, stan nie będzie współdzielony, modyfikacje będą dotyczyć zwróconej zmiennej state i będą się odbywać w niemutowalny sposób (kopie nowego stanu z modyfikacją)
Mam nadzieję, że to wszystko jasne. Teraz funkcja reducer, pierwszy rzut oka:
function reducer(state, action) {
if (!state.isActive && action.type !== "openAccount") return state;
switch (action.type) {
case "openAccount":
return {
...state,
balance: 500,
isActive: true
};
//(...)
default:
throw new Error("Unkown");
}
}
Funkcja reducer:
- Zdefiniowana powyżej poziomu RFC
- Jako taka jest definiowana jeden raz
- Jako taka zawsze ma tę samą referencę i tę samą definicję
- Będzie przekazana do useReducer jako parametr obok initial state
- Przyjmuje state i action
- State to stan, action ma typowo pod type rodzaj akcji, pod payload (albo innymi nazwami) argumenty (opcjonalne)
- Typowo wrzucamy action.type w switch statement
- Tutaj mamy jeszcze if-guard wykorzystujący stan (konto nieaktywne i akcja inna niż otwórz konto)
- Widzimy też niemutowalne operacje na stanie, przyjrzyjmy się bliżej
switch (action.type) {
case "openAccount":
return {
...state,
balance: 500,
isActive: true
};
case "deposit":
return { ...state, balance: state.balance + action.payload };
case "withdraw":
return { ...state, balance: state.balance - action.payload };
case "requestLoan":
if (state.loan > 0) return state;
return {
...state,
loan: action.payload,
balance: state.balance + action.payload
};
case "payLoan":
return { ...state, loan: 0, balance: state.balance - state.loan };
case "closeAccount":
if (state.loan > 0 || state.balance !== 0) return state;
return initialState;
default:
throw new Error("Unkown");
Ok, teraz użycie hooka useReducer w App:
export default function App() {
const [{ balance, loan, isActive }, dispatch] = useReducer(
reducer,
initialState
);
Mamy tutaj użycie tego hooka z czymś, czego jeszcze nie poznaliśmy – dekompozycja obiektu state, aby wyciągnąć z niego tylko te interesujące nas zmienne. Do zmieniania stanu i tak służy dispatch, zaś tak jest dużo czytelniej.
Teraz zobaczmy zwracany JSX:
return (
<div className="App">
<h1>useReducer Bank Account</h1>
<p>Balance: {balance}</p>
<p>Loan: {loan}</p>
<p>
<button
onClick={() => dispatch({ type: "openAccount" })}
disabled={isActive}
>
Open account
</button>
</p>
<p>
<button
onClick={() => dispatch({ type: "deposit", payload: 150 })}
disabled={!isActive}
>
Deposit 150
</button>
</p>
<p>
<button
onClick={() => dispatch({ type: "withdraw", payload: 50 })}
disabled={!isActive}
>
Withdraw 50
</button>
</p>
<p>
<button
onClick={() => dispatch({ type: "requestLoan", payload: 5000 })}
disabled={!isActive}
>
Request a loan of 5000
</button>
</p>
<p>
<button
onClick={() => dispatch({ type: "payLoan" })}
disabled={!isActive}
>
Pay loan
</button>
</p>
<p>
<button
onClick={() => dispatch({ type: "closeAccount" })}
disabled={!isActive}
>
Close account
</button>
</p>
</div>
);
}
Nie ma tu nic, czego byśmy wcześniej nie znali.