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.