Higher Order Reducers — Redux i powtarzanie kodu

Ten wpis jest 32 częścią z 41 w kursie React.js
wp-content/uploads/2017/10/React_logo_wordmark-300x101-1-e1508612391308.png
  1. React.js: Wprowadzenie do kursu od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux: globalny store i jednokierunkowy przepływ danych
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami
  40. Wady React Hooks
  41. React Hooks: Piszemy własne hooki!

Higher Order Reducers — co to takiego? Gdy popracujesz dłużej z Reduksem to na pewno zauważysz pewne powtarzalne wzorce. Napisanie akcji i reducerów do obsługi API to konieczność powtórzenia bardzo podobnego kodu kilka, kilkanaście razy! Czy na pewno jest to konieczne? Z pomocą przychodzą właśnie Higher Order Reducers i kompozycja.

Definicja Higher Order Reducer

Higher Order Reducer to funkcja, która zwraca reducer i (opcjonalnie) przyjmuje reducer jako argument.

Brzmi zrozumiale? Proste HOR są… proste 😉 Ale koncept jest bardziej rozbudowany niż się może początkowo wydawać, szczególnie jeśli weźmiemy pod uwagę kompozycję!

Zastosowanie

Weźmy sobie jako przykład operowanie na API. Pobieranie danych z endpointa. Musisz obsłużyć takie akcje:

  • pobieranie rozpoczęte
  • pobieranie zakończone sukcesem (dane)
  • błąd pobierania (błąd)

W podstawowej werji wygląda to tak jak poniżej. Action Creatory:

const dataFetchStarted = () => ({
  type: "FETCH_DATA_STARTED"
});

const dataFetchSucceeded = data => ({
  type: "FETCH_DATA_SUCCESS",
  payload: data
});

const dataFetchErrored = error => ({
  type: "FETCH_DATA_ERROR',
  payload: error
});

I do tego reducer:

const data = (
  state = { data: null, isLoading: false, error: null },
  action
) => {
  switch (action) {
    case "FETCH_DATA_STARTED":
      return { data: null, isLoading: true, error: null };
    case "FETCH_DATA_SUCCESS":
      return { data: action.payload, isLoading: false, error: null };
    case "FETCH_DATA_ERROR":
      return { data: null, isLoading: false, error: action.payload };
    default:
      return state;
  }
};

Natomiast sama akcja asynchroniczna (redux-thunk), która pobierze dane wygląda tak:

export const fetchData = () => (dispatch, getState) => {
  dispatch(dataFetchStarted());
  fetch("endpoint")
    .then(res => res.json())
    .then(json => dispatch(dataFetchSucceeded(json.results)));
    .catch(err => dispatch(dataFetchErrored(err)));
};

Problem

A teraz wyobraź sobie, że dane musisz pobierać, w bardzo podobny sposób, z 2, 3, 4… 10 endpointów. Czy to oznacza, że dla każdego z nich musisz zrobić kopiuj, wklej, znajdź i zamień na powyższym kodzie? Słabo…

Ale jest rozwiązanie: Higher Order Reducer

Rozwiązanie

Jak już wspomniałem, higher order reducer to taka funkcja, która zwraca reducer. W ten sposób możemy reducery tworzyć całkowicie dynamicznie. Na przykład tak:

const asyncReducerFactory = (name) => {
  return (state = { data: null, isLoading: false, error: null }, action) => {
    switch (action.type) {
      case `FETCH_${name}_STARTED`:
        return { data: null, isLoading: true, error: null };
      case `FETCH_${name}_SUCCESS`:
        return { data: action.payload, isLoading: false, error: null };
      case `FETCH_${name}_ERROR`:
        return { data: null, isLoading: false, error: action.payload };
      default:
        return state;
    }
  };
};

Jest to funkcja, która przyjmuje tylko nazwę i zwraca reducer. Nazwy akcji są tworzone dynamicznie — na podstawie podanego argumentu name.

Jak jej użyć? Zamiast całego poprzedniego reducera napisałbym teraz tylko:

const data = asyncReducerFactory('DATA');

A jeśli chciałbym stworzyć reducer dla pobierania kontaktów? Nic prostszego:

const contacts = asyncReducerFactory('CONTACTS');

I tak dalej. Kolejne użycia nie wymagają pisania więcej boilerplate’u.

Higher Order Action Creator

No, ale nadal mam sporo boilerplate’u, prawda? Na szczęście action creator też mogę generować dynamicznie:

const asyncActionCreatorFactory = (name, thunk) => () => {
  return dispatch => {
    dispatch({ type: `FETCH_${name}_STARTED` });

    return dispatch(thunk)
      .then(data => data.json())
      .then(json => dispatch({ type: `FETCH_${name}_SUCCESS`, payload: json }))
      .catch(err => dispatch({ type: `FETCH_${name}_ERROR`, payload: err }));
  };
};

Ponownie — na podstawie name generuję nazwy akcji. thunk to pewna asynchroniczna akcja, która (zakładam) wywołuje fetch i zwraca Promise.

Jak tego używam?

const fetchContacts = asyncActionCreatorFactory(
  "DATA",
  (dispatch, getState) => {
    return fetch("endpoint");
  }
);

A dla kontaktów?

const fetchContacts = asyncActionCreatorFactory(
  "CONTACTS",
  (dispatch, getState) => {
    return fetch(
      "https://randomuser.me/api/?format=json&results=10&seed=" +
        encodeURIComponent(getState().seed)
    );
  }
);

Tutaj dodatkowo „przemyciłem” parametr pochodzący z getState — tak samo jak we wpisie o redux-thunk.

Podsumowanie

W taki sposób, korzystając z funkcji wyższego rzędu, można uprościć wiele rzeczy w React i Redux. Tworzenie reducerów, action creatorów, a nawet komponentów! Mam nadzieję, że będziesz już potrafiła szybko stworzyć następne akcje i reducery dla kolejnych endpointów Twojego API! Sprawdź szkolenia Type of Web z React!

Jeśli chcesz na bieżąco dowiadywać się o kolejnych częściach kursu React.js to koniecznie śledź mnie na Facebooku i zapisz się na newsletter.

Nie wysyłamy spamu, tylko wartościowe informacje. W każdej chwili możesz się wypisać klikając „wypisz się” w stopce maila.

Ćwiczenie

Ćwiczenie: Zrefaktoruj kod z naszą listą kontaktów tak, aby skorzystać z napisanych w tym wpisie funkcji. Kod znajdziesz tutaj: github.com/mmiszy/typeofweb-kurs-react/tree/contacts-list-4-redux

Nawigacja po kursie:
  1. React.js: Wprowadzenie do kursu od podstaw
  2. Poznaj React.js
  3. Pierwszy komponent w React.js
  4. Props czyli atrybuty w React.js
  5. Podział na komponenty w React.js
  6. Klasy jako komponenty React.js
  7. Interakcja z komponentami React.js
  8. Stan komponentów React.js
  9. State w React.js 2
  10. Metody cyklu życia komponentu w React.js
  11. React.js w przykładach: filtrowanie statycznej listy
  12. Tworzenie aplikacji React.js dzięki create-react-app
  13. React.js na GitHub Pages dzięki create-react-app
  14. Testowanie aplikacji React.js — podstawy Enzyme
  15. Testowanie React.js w Enzyme — props, state i interakcje
  16. Poprawne bindowanie funkcji w React.js
  17. Odpowiadam na pytania: Babel, ECMAScript, destrukturyzacja, onClick, className
  18. Komunikacja pomiędzy komponentami w React.js
  19. Komunikacja z API w React.js
  20. Formularze w React.js — kontrolowane komponenty
  21. Formularze w React.js — niekontrolowane komponenty
  22. Odpowiadam na pytania: props, nawiasy klamrowe, funkcje vs klasy, import react
  23. TDD w React.js z pomocą react-testing-library
  24. Flux i Redux: globalny store i jednokierunkowy przepływ danych
  25. React + Redux — kurs: wprowadzenie i podstawy
  26. React + Redux — filtrowanie listy, proste selektory
  27. Projektowanie komponentów: Presentational & Container Components
  28. Asynchroniczność w Redux: redux-thunk
  29. Kiedy używać state, a kiedy Redux?
  30. Nowe metody cyklu życia: getDerivedStateFromProps i getSnapshotBeforeUpdate
  31. Leniwe ładowanie komponentów w React dzięki import
  32. Higher Order Reducers — Redux i powtarzanie kodu
  33. React Hooks — wprowadzenie i motywacja
  34. React Hooks: useState, czyli stan w komponentach funkcyjnych
  35. React Hooks: useState — wiele stanów, callbacki i inne niuanse
  36. React Hooks: useEffect — efekty uboczne w komponencie
  37. React Hooks a żądania do API
  38. useReducer — przenoszenie logiki poza komponent
  39. useMemo, useCallback, czyli rozwiązanie problemów ze zmieniającymi się propsami
  40. Wady React Hooks
  41. React Hooks: Piszemy własne hooki!

Nie wysyłamy spamu, tylko wartościowe informacje. W każdej chwili możesz się wypisać klikając „wypisz się” w stopce maila.

Subscribe
Powiadom o
guest
5 komentarzy
Most Voted
Newest Oldest
Inline Feedbacks
View all comments
Bartek Pawelec
Bartek Pawelec
2 lat temu

Będzie coś jeszcze czy na tym koniec?

heisenberg8
heisenberg8
2 lat temu

Cześć Michał, co w przypadku gdy w kilku reducerach powtarza się dany kod, ale tylko w części reducera. Np te akcje do fetchowania, natomiast każdy z nich ma dodatkowe, swoje własne akcje. Czy wtedy możemy jakoś stworzyć HOR dla tych 3-4 akcji i użyć go wewnątrz danego reducera?

Przemysław Kosior
1 rok temu

Super!

Back to Top
Zobacz też:
GitHub Pages i create-react-app
React.js na GitHub Pages dzięki create-react-app

Bardzo często początkujący pytają mnie gdzie mogą łatwo wrzucić nieco bardziej rozbudowany projekt, żeby go pokazać. Nie mają swojego hostingu, na Codepenie nie będzie to wygodne jeśli aplikacja podzielona jest...

Zamknij