Введение
Всем привет :) Хотел бы рассказать об использовании RTK Query + Redux Toolkit в больших проектах, а также ускорить вашу разработку и добавление новых страничек путем избавления от ненужных дублирований кода.
Если ты frontend-разработчик на react, то думаю вам не впервой сталкиваться с использованием RTK Query вместе с Redux Toolkit. Но даже если это не так, то думаю мне есть что тебе показать новое ;) А если ты ни разу не сталкивался с этой связкой, то возможно после этой статьи задумаешься о её использовании.
Предисловие
Рекомендуется скачать проект и по ходу чтения заглядывать в код, ибо некоторые моменты проще увидеть и понять, чем читать десятки слов и предложений.
Установка зависимостей: yarn install
. Запуск по команде: yarn dev
.
Основные проблемы данной связки библиотек
Необходимо постоянно писать лоадеры, которые бы показывали загрузку страницы, а затем отрисовывали бы полученные данные.
Иногда хочется получить просто данные из запроса без создания отдельного слайса для страницы, а когда-то наоборот.
Всегда необходимо думать об обработке неожиданных ошибок при добавлении новых страничек в сервис и отрисовывать окошко ошибки. При это хочется избежать использования глобального обработчика.
Если необходимы 2 и более запроса, то приходится отдельно прописывать логику и объединять запросы. А если один из них провалится, прописывать логику повторного запроса.
При добавлении данных в слайс в методе onQueryStarted, данные просто не успевают попасть в хранилище до отрисовки компонента, который его использует.
При запросе какой-либо мутации может инвалидироваться тег, что вызовет перезагрузку всей страницы, даже когда в этом нет необходимости.
Спешу вас обрадовать, я эти проблемы решил и причем с минимальным количеством кода, поэтому давайте же перейдем к решению и самому проекту.
Структура проекта
Всю основную логику помещаем в папку redux. Разделяем api-запросы и слайсы по отдельным папочкам. Для лучшего понимания представляйте api, как верхний слой над слайсами. Api работает, проводит какие-то свои манипуляции и кладет их в хранилище. Хранилищу необязательно знать, кто и что в него кладет, ему лишь надо знать, что надо с сделать с данными. Api может обращаться к слайсам, но слайсы не могут обращаться к api.

Полезные для использование хуки и компоненты высшего порядка кладем в соответствующие папки hooks и hoc.

Страницы в Next.js находятся в особенной папке pages. Все примеры использования в cat-facts/index.tsx

Проблема и решение
При добавлении данных в слайс в методе onQueryStarted, данные просто не успевают попасть в хранилище до отрисовки компонента, который его использует.
Переопределяем baseQuery
и принимаем через аргументы метод onSuccess
, который позволит нам помещать данные в хранилище, а после этого завершать загрузку. Не забываем вызов обернуть в try catch, чтобы видеть ошибки в консоли.
Код

Пример использования слайсов и апи методов


Необходимо постоянно писать лоадеры, которые бы показывали загрузку страницы, а затем отрисовывали бы полученные данные.
Создаем специальный «хок», который будет делать запросы и выводить сообщения в случае ошибки. Логика его достаточна сложна, поэтому обратитесь к коду проекта. Находится в одноименной папке src/hoc
. Можете адаптировать его под свою архитектуру проекта.
Для тех кто разбирается в TS
Типизация при использовании работает отлично, однако от костыля при преобразовании к any, я так и не смог избавиться. Если кто-то знает, как решить, прошу Merge Request в репозиторий кинуть.


Если необходимы 2 и более запроса, то приходится отдельно прописывать логику и объединять запросы. А если один из них провалится, прописывать логику повторного запроса.
Прежде чем использовать метод, создаем страничку, которая будет отрисовывать данные. Чтобы использовать данные от запросов, объединяем типы наших резолверов и наследуемся от них.

Далее используем «хоки» для обработки запросов к api:

withQueryResolver – содержит в себе самый главный запрос, который потом можно принять через аргументы и использовать данные. Вызывать можно только 1 раз и перед withOtherQueryResolver
withOtherQueryResolver – содержит в себе второстепенный запрос, которые нельзя принять через аргументы. Обычно используется, чтобы какие-то дополнительные данные положить в хранилище (слайсы). Должен идти после withQueryResolver. Могут быть вызваны друг за другом несколько раз. При ошибке перезагрузка данных будет только у тех, что завершились ошибкой, а не у всех сразу.
withMutationResolver – отвечает за обработку мутации, показа модалки загруузки, вывод модалки с ошибкой (если нужно). Можно адаптировать под свой проект.
Как мы видим процесс загрузки и объединения запросов занимает всего 2 строчки, а подключение логики с обработкой мутации всего 1 строчку.
И наконец прописываем страницу, которая будет экспортироваться во внешку:

Все аргументы объединяются для каждого хука, поэтому можно избежать дублирования.
При запросе какой-либо мутации может инвалидироваться тег, что вызовет перезагрузку всей страницы, даже когда в этом нет необходимости.
Так как мы данные получаем из слайсов, то нам не нужно делать запрос заново, поэтому вызываем метод disableReload перед запросом мутации. В таком случаем при успешном вызове наш тег инвалидируется, но это не спровоцирует перезагрузку страницы.

Метод showRetryModal используем, если хотим при ошибке показать модалку, что запрос не прошел и предложить повторить запрос.
Итог
При таком подходе удается создать в проекте устойчивую к росту архитектуру и облегчить разработку. Давайте посмотрим, чего мы добились:
При добавлении новой страницы нам необходимо написать всего 2-3 строчки и логика обработки запроса уже готова. Далее остается только написать контрол, который уже будет работать с этими данными. Теперь можно не бояться, что при какой-то неожиданной ошибке у нас вместо красивого сообщения об ошибке будет белый экран или еще чего страшнее.
В случаем изменении какого-то api метода, «хоки» начнут ругаться, что им передали контроллер, который принимает совсем другие данные, нежели те, что api-хук. А значит вероятность выпустить релиз с багами уменьшается в разы, ведь в production такое не скомпилируется.
Благодаря объединению аргументов их можно использовать, как в компоненте приемнике, так и в других методах api. Например, миграция одного аргумента метода к другому методу это дело 3 секунд. Добавление какого-то нового аргумента тоже менее болезненна, ведь если он передается для какого-то другого api метода, правки не нужны и typescript не будет ругаться.
Если нам достаточно данных из хука, мы можем не создавать отдельный слайс под данные и соответственно не писать лишнего кода. Например, если наша страница всего лишь отображает данные, зачем писать для этого отдельный слайс? Можно просто взять готовые данные и отобразить.
Вся логика обработки ошибок находится в одном едином месте. В будущем при необходимости мы можем легко добавить сбор какой-то статистики или еще что-то.
Ionenice
От статьи в 2022 про redux toolkit, rtk и ts хотелось бы видеть сильно больше, чем скрины кода и ссылку на гитхаб. Интересно читать про то, как писать правильные костыли в этой связке или как максимально их избегать. Или под «большими проектами» изначально подразумевались проекты с множеством однотипных страниц где всё что нужно это очередной запрос и его loader?
AleksanderZverev Автор
Я предлагаю архитектуру проекта, которая помогла бы за короткие сроки сделать качественный продукт, который с увеличением кода не становится сложнее и остается гибким к расширению и улучшению. Поэтому да, под большими проектам тут подразумеваются проекты, у которых большое количество страничек со своей логикой обращения к апи.
В добавок особых такого рода статей я не нашел, поэтому решил завести дискуссию об этом сам. Глядишь, может кто-то предложит лучше :)
Ionenice
Возможно стоило чуть больше акцентировать внимание, что это стартер, но это ваша статья)
Просто начав изучать rtk, redux + в связке с ts я столкнулся с кучей проблем, недопонимания, из-за чего пришлось изучать костыли, которые люди предлагают для тех или иных задач. Возможно, каких-то сложных решений мне и хотелось видеть в статьях про rtk