Сегодня рассказ пойдет о двух удобных способах сократить рутину в Redux, которые мы используем в разных частях собственного проекта. Речь пойдет о малоизвестной библиотеке Type to reducer и довольно функциональном Redux toolkit. Почему нам было трудно жить с чистым Redux, зачем нужны вспомогательные инструменты и как они облегчают жизнь, читайте под катом.
Статья написана по итогам внутреннего митапа для нефронтендеров, проходившего пару месяцев назад, и рассчитана не только на тех, кто в теме.
Все интерфейсы в собственном продукте Максилекта собраны на React. И раз уж мы ориентируемся в рассказе на неподготовленную аудиторию, начнем с него.
В логике React-а интерфейс собирается из компонентов, у каждого из которого есть свое состояние. В голом React приложении это состояние может передаваться только по иерархической цепочке компонентов сверху вниз - эдакий однонаправленный поток данных.
Это удобная концепция для небольших проектов. Однако когда приложение разрастается и состояние становится большим (туда включаются текущие авторизованные пользователи и т.п.), фронтенду приходится хранить на своей стороне очень много данных. Возникает задача передачи этих данных между компонентами. И начиная с определенного масштаба делать это, используя только однонаправленный поток, неудобно. Работать с таким кодом становится больно.
И здесь на помощь приходит Redux - хранилище состояний JavaScript приложений. Redux избавляет от необходимости дублированного хранения информации о состоянии в самих компонентах. В целом он помогает навести порядок и упрощает работу с кодом (а заодно и повышает производительность приложения).
При использовании Redux компоненты вызывают dispatch для изменения состояния. В него передается action, который и сообщает, что именно с этим состоянием нужно сделать. Далее срабатывает reducer, где описана бизнес-логика изменения, и новое состояние попадает во все компоненты приложения, которые используют этот кусок данных. В результате пользователи видят в интерфейсе актуальную информацию.
По мере дальнейшего роста приложения и здесь возникают свои сложности.
В большом приложении настройка Redux с нуля напоминает запуск космического корабля, особенно если ты раньше никогда не имел с ним дела. Чтобы настроить хранилище, нужно скопировать очень большой объем шаблонного кода. А еще чтобы заставить Redux делать нечто полезное, скорее всего придется использовать дополнительные библиотеки. Хотите видеть во время отладки актуальное состояние в браузере - ищите библиотеку, нужна синхронная передача - тоже. В итоге проект растет как снежный ком.
В этот момент ты обращаешься к вспомогательным инструментам, облегчающим работу с Redux, о которых я и хочу сегодня рассказать.
Type to reducer
Когда я пришел на проект, Type to reducer там уже использовался. С него и начну свой рассказ.
Type to reducer - это маленькая библиотека, которая содержит одноименную функцию, облегчающую создание reducer. Это довольно непопулярная штука, о которой на Хабре, кажется, не писали. И на GitHub у нее всего 125 поклонников (https://github.com/tomatau/type-to-reducer). Подозреваю, что когда проект начинался, более популярных инструментов для решения той же задачи еще не существовало. Сейчас выбор может показаться странным, но на тот момент он был вполне оправдан.
Функция Type to reducer принимает на вход объект reducer map. Это коллекция из пар ключ-значение, где в качестве ключа выступает имя Action, а в качестве значения - функция reducer, которая возвращает измененный Store.
В качестве примера приведу фрагмент кода из нашего проекта.
Reducer под названием accountManaging при срабатывании action FETCH_LIST_START меняет параметр isLoading на true (в интерфейсе пользователь видит прелоадер и понимает, что файл загружается). Он умеет обрабатывать успешную загрузку (FETCH_LIST_SUCCESS) - снова переводит isLoading в false и возвращает некий список - пробрасывает данные, полученные с сервера. Также обрабатывает ошибки (FETCH_LIST_ERROR).
Вторым аргументом передается начальное состояние компонент (accountManagingInitState).
В классическом Redux action и reducer разделены между собой и это усложняет разработку. Type to reducer объединяет их в общую пачку, где уже описано новое состояние приложения. Фактически, это просто небольшая утилита для организации reducer-ов. И в целом в нашем проекте Type to reducer вполне решает поставленную задачу.
Redux toolkit
Некоторое время назад нам потребовалось выпилить весь бизнес из проекта, оставив чистый boilerplate код, чтобы переиспользовать его в другом модуле. На этом этапе мы приняли решение отказаться от непопулярного Type to reducer, заменив чем-то поинтереснее - Redux toolkit. Если Type to reducer - это просто небольшая вспомогательная утилита, то Redux toolkit - полноценный набор инструментов. Решает он ту же задачу, но делает это более элегантно.
Redux toolkit - полноценный пакет для Redux-разработки. В отличие от первого инструмента, у него 8,4 тыс. поклонников на GitHub (https://github.com/reduxjs/redux-toolkit). Кстати, для работы с Redux toolkit отдельно устанавливать Redux нет необходимости - у него все есть “из коробки”, в том числе классические инструменты разработчика для отладки кода.
Чтобы нагляднее показать возможности Redux toolkit, я написал простое приложение - покажу его реализацию на чистом Redux и с Redux toolkit.
Представим, что у нас есть простой счетчик с начальным значением 0. У него есть две кнопки - плюс и минус.
Вот, как выглядит создание и использование Store на классическом Redux:
Левая часть в пояснениях не нуждается - здесь мы просто создаем Store.
В правой части мы сначала объявляем текстовые константы, потом типы action (это то же самое, что в Type to reducer мы передавали в качестве ключа, изменяющего свойства функции). После этого задается начальное состояние - 0.
Далее reducer принимает action и в зависимости от его типа возвращает новое состояние - увеличенное или уменьшенное на единицу относительно текущего (далее в коде размещаются action creator-ы).
Казалось бы, все наглядно. Проблема в том, что при разрастании проекта action creater-ов становится очень много. Обрабатывая каждый из них, приходится совершать много рутинных действий. И проще это делать через инструмент - Redux toolkit.
В этом случае кода гораздо меньше. Слева, как и в прошлый раз, создание Store. В отличие от предыдущего раза мы не подключаем инструменты разработчика, поскольку в Redux toolkit есть все необходимое. Справа, опять же, начальные значения и action-ы, которые задаются при помощи функции createAction. А ниже мы задаем reducer.
Код похож на тот, что мы писали с использованием Type to reducer. Но на мой взгляд, тут есть одно интересное отличие. В соответствии с концепцией классического Redux, мы не можем мутировать состояние. Каждый раз мы не меняем объект, а возвращаем новый - с другим состоянием. А в Redux toolkit нам как бы доступна мутация состояния (хотя на самом деле все это синтаксический сахар, который превращается в обычный Redux под капотом движка библиотеки). С точки зрения синтаксиса это удобнее и понятнее.
На проекте мы делаем несколько иначе - используем слайсы (это тоже часть библиотеки Redux Toolkit). Если store - это все состояние нашего приложение, то слайс - это его срез (часть). Мы можем сделать отдельный срез для пользователей, уведомлений, заметок и т.п. Это достаточно удобно. Мы не получаем нового функционала, но можем еще сократить код.
Для создания срезов используется функция createSlice, которая есть в Redux toolkit. На вход она принимает имя среза и начальное состояние. Далее мы описываем reducer-ы по аналогии с тем, как делали это выше. Возвращает эта функция объект slice, который содержит сгененрированные функции reducer-ов и action-ы внутри объекта. Так код становится еще компактнее.
В целом оба инструмента помогают нам сокращать рутину на проектах. Но Redux toolkit более свежий и функциональный. Если вы начинаете большой проект на Redux с нуля, советую как минимум задуматься о его использовании.
Статья написана по материалам внутреннего выступления Дмитрия Титова.
P.S. Мы публикуем наши статьи на нескольких площадках Рунета. Подписывайтесь на нашу страницу в VK или на Telegram-канал, чтобы узнавать обо всех публикациях и других новостях компании Maxilect.