Higher Order Reducers — Redux i powtarzanie kodu

Ten wpis jest 32 częścią z 39 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

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! Poznaj React na naszym szkoleniu!

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.

Ć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