Так вышло, что в данный момент я принимаю участие в разработке фронт-энд приложения (React + Redux), делающего множество запросов к REST API каждую минуту, если не секунду.
Мне надоело на каждый запрос писать REQUEST/FAILURE/SUCCESS (далее RFS) экшны, к ним кейсы для редьюсера, всё это обильно поливать тестами (ведь качество превыше всего).
Я написал очередной велосипед.
Существующая проблема
Уже написано множество библиотек для удобной работы с RFS, но они предполагают детальную настройку каждого из трёх экшнов + написание кейсов для редьюсера + тесты. В основном данные библиотеки можно использовать в 100% случаев написания запросов к серверу. Такая гибкость требует написания массы кода для однотипных задач.
В проекте, которым я занимаюсь в данный момент, 90% запросов выполняют предельно простую задачу: сходи на сервер, возьми данных, преобразуй их немного, положи в state, если что-то пошло не так — жалуйся. Написаны тысячи строк тестов, по своей сути являющихся копипастой с микро-изменениями.
К сути
Доколе? Выходные перед монитором, литр кофе, шаверма на обед и ужин, немного контролируемой магии...
… библиотека готова.
Функция fromTo(from, to, [through]) возьмёт данные где сказано, преобразует как надо и положит в указанное место (любое в вашем state, но только если для редьюсера вы используете библиотеку immutable). Редьюсер самостоятельно (после обёртки его во fromTo.wrapper
) поймёт как работать с данными и предсказуемо изменит state в соответствии с RFS экшнами (о которых библиотека позаботится сама). Тестами покрыто всё (если найдёте багов — давите несчадно или откройте тикет).
Краткое описание возможностей
Простейший способ (на нашем проекте это около половины случаев) использования fromTo экшна это:
// запрос к серверу возвращает {"mood": "happy"} , HTTP200
dispatch(fromTo(
() => axios.get('dogs.io/good-boy'),
['dogs', 'goodBoy'],
));
Вот что в это время будет происходить в вашим state:
Начальное состояние
{
...otherReducers,
dogs: Immutable.fromJS({}),
}
REQUEST
{
...otherReducers,
dogs: Immutable.fromJS({
goodBoy: {
isRequesting: true,
},
}),
}
SUCCESS
{
...otherReducers,
dogs: Immutable.fromJS({
goodBoy: {
isRequesting: false,
data: {
mood: 'happy',
},
},
}),
}
Ещё для 40% случаев используется объект в качестве аргумента to
и добавляется третий (опциональный) аргумент. Востальных случаях действуем по-старинке (fromTo
не панацея, а удобный инструмент).
Разбор функции fromTo(from, to, [through])
Функция имеет 3 аргумента.
-
from
. Говорим где взять данные. Функция, что возвращает Promise. Будет вызвана без аргументов. -
to
. Говорим куда сохранять данные.- Если использовать объект в качестве аргумента, то необходимы 3 ключа:
{ request, failure, success }
. Это 3 координаты для данных в вашем state, куда будут сохраняться:
request
: координаты для булевого значения завершен ли вызовfrom
(например[ 'dogs', 'isFetching', 1 ]
),
failure
: координаты для данных, что вернулись приreject
(например[ 'cats', 'errors', 2 ]
),
success
: координаты для данных, что вернулись приresolve
(например[ 'robots', 'data', 3 ]
). - Можно ограничиться списком (например
[ 'goodBoy' ]
), тогда данный аргумент будет преобразован в объект{ request: [ 'goodBoy', 'is Requesting' ], failure: [ 'goodBoy', 'error' ], success: [ 'goodBoy', 'data' ] }
- Если использовать объект в качестве аргумента, то необходимы 3 ключа:
[through]
. Говорим (а можем и не говорить и довериться дефолтам), как преобразовать данные, что вернулись в следствиеfrom()
. Объект может содержать один или оба из методов:requestAdapter
— функция, что получит на вход данные изresolve
. Вернувшиеся данные будут сохранены при SUCCESS,errorAdapter
— функция, что получит на вход данные изreject
. Вернувшиеся данные будут сохранены при FAILURE.
Итог
В данный момент на проекте мы экономим часы нашего времени и пучки нервов благодаря отсутствию необходимости писать тонны однообразного кода. Вместо 90% экшнов, редьюсеров, тестов — вызывается одна функция dispatch(fromTo(...args))
. Мы рады.
Конечно, некоторое количество тестов всё же пишется, но в основном это тесты на аргумент through
(обязательно) и дань TDD.
В комментариях хотелось бы увидеть мнение коллег по цеху, конструктивная критика более чем приветствуется. Сырцы в гите, библиотека в энпээме, утка в зайце, заяц в сундуке, а тот на дереве.
Хорошего дня.
Автор: emigrant90