[Из песочницы] Совершенствуем Redux
Привет, Хабр! Представляю вашему вниманию перевод статьи «Redesigning Redux» автора Shawn McKay.
Должно ли управление состоянием вызывать проблемы на сегодняшний день? Интуитивно, разработчики видят скрытую правду: управление состоянием куда сложнее, чем должно быть. В данной статье мы разберем несколько вопросов, которые вы наверняка задавали себе:
- Вам действительно необходима библиотека для управления состоянием?
- Заслужил ли Redux свою популярность? Почему или почему нет?
- Можем ли мы придумать лучшее решение? Если да, то какое?
Необходима ли библиотека для управления состоянием?
Front-end разработчик не тот, кто попросту передвигает пиксели из стороны в сторону; истинное искусство в знании где хранить состояние. Это кажется сложным только на первый взгляд.
Давайте рассмотрим возможности, которые предоставляет нам React:
1. Состояние компонента (Component State)
Состояние хранится внутри компонента. В React мы обновляем state
через setState()
.
2. Относительное состояние (Relative State)
Состояние переданное от родителя потомку. В React передаем props
как свойство компонента потомка.
3. Переданное состояние (Provided State)
Состояние хранится в поставщике (provider), и доступно любому компоненту (consumer), расположенному ниже по дереву. Context API
в React.
View хранит большую часть состояния. Но что делать с остальным кодом, который отражает основные данные и логику?
Размещение всего кода внутри компонентов может привести к низкому разделению ответственности: растет зависимость от view-библиотек, усложняется тестирование такого кода, но самое страшное: приходится регулярно менять способ хранения состояния.
Структура приложения со временем изменяется, и с каждым разом сложнее определить, какому компоненту необходимо то или иное состояние. Проще всего вынести все состояние из корневого компонента, что можно осуществить следующим способом.
4. Внешнее состояние (External State)
Состояние может находиться отдельно от компонентов, которые синхронно «свяжуться» с ним при помощи provider/consumer паттерна. Вероятнее всего самой большой популярностью, среди библиотек для управления состоянием, пользуется Redux. В течении последних двух лет она получила большую известность среди разработчиков. Так в чем причина такой любви к одной библиотеке?
Redux более производительный? Нет. На самом деле, работа приложения немного замедляется с каждым новым действием, которое должно быть обработано.
Redux прост в применении? Конечно нет.
Простым был бы нативный javascript:
Так почему каждый не может использовать global.state = {}
?
Почему Redux?
Под капотом, Redux аналогичен глобальному объекту TJ, только обернут рядом утилит.
В Redux можно непосредственно изменять состояние, путем передачи (dispatch) действий (action) через указанные инструменты.
Библиотека включает два вида обработчиков действий: middleware & subscriptions. Middleware — это функции, которые перехватывают действия. Включают такие инструменты как «logger», «devtools» или «syncWithServer». Subscriptions — это функции, используемые для отправки изменений компонентам.
Наконец, редьюсеры (reducer) — это функции, которые изменяют состояние и делят его на мелкие, модульные и управляемые части.
Вероятнее всего, Redux более применим для хранения состояния, чем глобальный объект.
Думайте о Redux как о глобальном объекте с расширенными возможностями и упрощенным способом «преобразования» состояния.
Настолько ли сложен Redux?
Да. Есть несколько неоспоримых признаков, что необходимо улучшить API; можно сделать вывод при помощи следующего уравнения:
Считаем, что time_saved подразумевает время затраченное на разработку собственного решения, а time_invested равняется часам потраченным на чтение документации, прохождение обучающих курсов и изучение новых понятий.
Redux, в принципе, простая и небольшая библиотека с крутой кривой обучения. На каждого разработчика, который овладел и извлек выгоду из Redux, погрузившись в функциональное программирование, найдется другой потенциальный разработчик, запутавшийся и думающий «это все не для меня, я возвращаюсь к jQuery».
Вы не должны разбираться что такое «comonad», используя jQuery, и не обязаны понимать функциональную композицию, чтобы справиться с управлением состоянием.
Цель любой библиотеки: сделать сложное простым при помощи абстракции.
Я не намерен высмеивать Дэна Абрамова. Redux стал популярным на слишком ранней стадии своего развития.
- Как внести изменения в библиотеку, которую используют миллионы разработчиков?
- Как вы оправдаете критические изменения, которые повлияют на проекты во всем мире?
Вы не сможете. Но предоставив расширенную документацию, обучающие видео и помощь комьюнити, вы окажете неоценимую помощь. У Дэна Абрамова получилось это.
А может есть другой путь?
Совершенствуем Redux
Redux заслуживает изменений, и я вооружился шестью его слабыми местами, чтобы доказать это.
1. Настройка
Предлагаю посмотреть на первоначальную настройку Redux-приложения (левый скрин).
Много разработчиков, сразу после первого шага, остановились в недоумении. Что такое thunk? compose? Способна ли функция на такое?
Считается, что Redux основан на конфигурации над композицией. Настройка должна быть похожа на пример справа.
2. Упрощаем редьюсеры
Редьюсеры в Redux могут использовать switch-конструкции далекие от тех, которые мы привыкли использовать.
Учитывая, что редьюсеры находят соответствие по типу действия, мы можем сделать каждый редьюсер чистой (pure) функцией, принимающей состояние и действие. Можно сократить действие и передавать только состояние и данные.
3. Async/Await без Thunk
Thunk широко используется для создания асинхронных действий в Redux. Во многих отношениях, thunk больше похож на умный хак, чем на официально рекомендуемое решение. Как это работает:
- Вы передаете действие, уже как функцию, а не объект.
- Thunk проверяет каждое действие, что оно является функцией.
- Если все сходится, thunk вызывает эту функцию и передает в нее некоторые методы стора: dispatch и getState.
Серьезно? Следует ли таким образом типизировать простые действия как объект, функцию или даже Promise?
Возможно ли использовать async/await, как в примере справа?
4. Два вида действий
Задумайтесь, ведь действительно есть два вида действий:
- Reducer action: запускает редьюсер и изменяет состояние.
- Effect action: запускает асинхронное действие. Может вызвать reducer action, но асинхронная функция не способна напрямую изменить состояние.
Умение различать виды действий принесет больше пользы, чем использование «санков».
5. Никаких больше переменных хранящих тип действия
Почему принято разделять генераторы действий (action creators) и редьюсеры? Может ли один существовать без другого? Как изменить один не изменяя другой?
Генераторы действий и редьюсеры две стороны одной медали.
const ACTION_ONE = 'ACTIONE_ONE'
— это лишний побочный эффект разделения генераторов действий и редьюсеров. Обращайтесь с ними как с единым целом и отпадет потребность в крупных файлах с экспортом типов.
6. Редьюсеры — это генераторы действий
Объединяйте элементы Redux по их назначению, и вы получите простой шаблон.
В итоге, следуя этому сценарию, редьюсер может стать генератором действий.
Используйте соглашение об именах, и следующие пункты будут довольно предсказуемы:
- Если редьюсер получил название «increment», тогда тип будет «increment». Даже лучше, обозначим как «count/increment».
- Каждое действие передает данные через «payload».
Теперь при помощи count.increment
мы можем создать генератор действий напрямую из редьюсера.
Хорошие новости: мы можем улучшить Redux
Эти проблемные части мотивировали на создание Rematch.
Rematch служит оберткой вокруг Redux, предоставляя разработчикам упрощенное API.
Вот полный пример кода с Rematch:
Я использовал Rematch в production последние несколько месяцев. И что я думаю:
Я никогда не тратил так мало времени на управление состоянием.
Redux не исчезнет и не обязан. Освойте эту библиотеку с меньшей кривой обучения, меньшим количеством бойлерплейта и меньшими умственными затратами.
Опробуйте Rematch и выясните, нравится он вам или нет.
Поставьте звезду, чтобы позволить узнать о нас другим.