Избавляемся от генерации шаблонного кода при помощи redux-actions-async
Организация асинхронных экшенов в вашем Redux приложении на примере использования NASA API
Связка React-Redux с момента создания “славилась” большим количеством кода, который нужно копировать от одного модуля к другому. Каждая команда борется с этим по-своему либо не борется вовсе.
При беглом осмотре я заметил большую активность в топиках по обсуждению данной темы: https://github.com/reactjs/redux/issues/2295. Есть даже “официальное” частичное решение проблемы, описанное здесь: https://github.com/reactjs/redux/blob/master/docs/recipes/ReducingBoilerplate.md
При использовании шаблонного кода можно выделить такие преимущества:
- прозрачность — каждый модуль состоит из стандартного кода и новый разработчик быстрее вникнет в проект, не изучая при этом дополнительное API генераторов;
- расширяемость — любой участок модуля может быть переписан или дописан под нужды бизнес-задачи;
- переносимость — модуль легко переносится в другой проект с иным подходом к обработке экшенов.
Но при этом есть и существенные недостатки:
- отсутствие одного стиля — каждый программист будет писать модуль так, как он видит;
- большая вероятность допустить ошибку при копировании;
- возможность мутации состояния неопытными разработчиками;
- дополнительные затраты на тестирование шаблонного кода.
В результате появилась идея как можно легко обернуть создание экшенов и редьюсеров в функции, оставляя при этом возможность создавать свои под специфические нужды. Ниже приведены несколько этапов, которые покажут использование этого подхода на примере загрузки данных с NASA API и выводом данных в контейнер.
В конце статьи вы найдете ссылку на репозиторий, где сможете ознакомиться с примером реализации.
Этап 1: Определяем тип экшена
Этот этап ничем не отличается от классического подхода создания экшенов в Redux. Подробнее о классическом подходе можно найти в официальной документации: https://redux.js.org/docs/basics/Actions.html#actions
Для примера создадим новый экшн-тип для получения данных с сервера:
// file: actionTypes/nasaActionType.jsexport const NASA_DATA_FETCH = 'NASA_DATA_FETCH';
Этап 2: Создаем функцию для обращения к серверу
В качестве примера возьмем открытое API от NASA для получения данных о каком-либо факте. На этом этапе мы увидим обычную функцию с использованием axios или fetch, которую мы регулярно используем в работе.
// file: endpoints/nasa.jsimport axios from 'axios';
export const fetchPlanet = () =>
axios.request({
url:
'https://api.nasa.gov/planetary/apod?api_key=NNKOjkoul8n1CH18TWA9gwngW1s1SmjESPjNoUFo',
method: 'get',
});
Этап 3: Создаем наш первый асинхронный экшн
Третий этап посвящен написанию action-creator, который будет генерировать 3 экшена:
- информирование стора о начале запроса (NASA_DATA_FETCH_REQUEST)
- информирование стора об успешном запросе (NASA_DATA_FETCH_SUCCESS)
- информирование стора о какой-либо ошибке (NASA_DATA_FETCH_FAIL)
Очевидно, что эту генерацию мы можем упаковать в одну функцию createAsyncAction:
// file: /actionCreators/nasa.jsimport { fetchPlanet } from '../endpoints/nasa';
import { createAsyncAction } from 'redux-actions-async';
import { NASA_DATA_FETCH } from '../actionTypes/nasaActionType';
export const nasaFetchPlanet = createAsyncAction(
NASA_DATA_FETCH,
fetchPlanet
);
В коде мы использовали action-type из этапа 1 и функцию-запрос из этапа 2. В результате мы имеем action-creator, который можем прокинуть в любой контейнер:
import * as actionCreators from './actionCreators/nasa';const mapDispatchToProps = {
nasaFetchPlanet: actionCreators.nasaFetchPlanet,
};
Этап 4: Обработка экшенов в редьюсере
Последний этап направлен на обработку сгенерированных экшенов для использования загруженных данных в наших контейнерах и компонентах. Для этого воспользуемся функцией handleAsyncActions, которая обработает все три экшена, сгенерированные функцией createAsyncAction:
import { handleAsyncActions } from 'redux-actions-async';
import { NASA_DATA_FETCH } from '../actionCreators/nasaActionCreators';
export default handleAsyncActions(NASA_DATA_FETCH);
Что получилось?
Теперь мы можем использовать наш стор для вывода данных на экран, используя React-компоненты:
class Planet extends Component {
componentWillMount() {
this.props.nasaFetchPlanet();
}
render() {
const { loading, planet } = this.props;
if (loading || !planet) {
return <span>Loading...</span>;
}
return (
<div>
<h1>{planet.title}</h1>
<img src={planet.url} width="500" />
</div>
);
}
}
const mapStateToProps = state => ({
loading: state.loading,
planet: state.data,
});
const mapDispatchToProps = {
nasaFetchPlanet: actionCreators.nasaFetchPlanet,
};
export default connect(mapStateToProps, mapDispatchToProps)(Planet);
Результат не заставит нас ждать:
Итоги
При желании мы легко можем избавиться от бесконечного копипаста шаблонного кода. Как именно организовывать архитектуру приложения — забота программиста и конкретной команды. Я лишь показал малую часть того, как это делаем мы внутри нашей компании.
В следующих статьях мы рассмотрим другие проблемы, которые может решить этот подход создания экшенов:
- использование normalizr при работе с большим количеством повторяемых объектов;
- быстрое создание пагинации;
- единый источник обработки ошибок;
- отслеживание спинера на странице для нескольких источников данных.
Ссылка на библиотеку:
Ссылка на демо-проект: