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 wersji 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! zapisz się na szkolenie 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.
Ć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