Новый контекст Реакта
или жизнь без боли и бойлерплейта
Предисловие и боль
Наверное, каждый фронтендер уже слышал, что 29 марта вышла новая версия React, которая включает в себя новый контекст. Но, возможно, не все до конца понимают, что́ она приносит с собой, и что управление состоянием может выглядеть совершенно по-другому.
Я достаточно давно пишу на стеке React-экосистемы и для управления глобальным состоянием использую Redux. Первое время я был вдохновлён тем, как просто и здорово всё получается. Сначала я писал всё в отдельных файлах: actions.js, constants.js, reducer.js и так далее. Потом мне надоело ходить из папки в папку и я начал использовать подход ducks, но с каждым новым редьюсером мне всё меньше нравилось писать бойлерплейт.
Я начал чувствовать, что пишу одно и то же, что каждый раз мне нужно написать action type, затем action creator, добавить action type в редьюсер и подключить всё в коннекте контейнера – и так по бесконечному кругу. Появилась потребность в абстракциях: тогда уже была выпущена библиотека redux-actions, но мне никогда не нравились подходы, которые могут связать мне руки. Это казалось полумерой и не решало моих проблем.
И тут вышел новый контекст. Написание нескольких демок помогло понять, что потенциал контролирования состояния огромен. Но не хватало времени проверить всё в работе, и я отложил эксперименты до лучших времён.
Недавно мне всё-таки удалось проверить новый контекст в продакшене, и мне очень хочется рассказать сообществу о том, что это отличный способ контролирования глобального состояния, который позволяет писать меньшее количество кода, при этом сохраняя необходимую функциональность.
Краткий обзор нового подхода
Тем, кто ещё не до конца знаком с новым контекстом, советую прочитать статью Кента Додса, где доступно объясняется суть обновления. Также есть официальная документация с примерами.
На самом деле всё просто:
И это всё, что нужно знать. Но я пошёл немного дальше и создал несколько удобных абстракций для работы.
Сделаем жизнь разработчика проще
Чем меньше неудобств мы испытываем при написании кода, тем лучше становится собственно наш код и наша жизнь.
Вместо того, чтобы каждый раз импортировать консьюмер для использования контекста, проще создать универсальный HOC. А ещё было бы удобно собрать всех провайдеров в одном месте и подключать их из этого файла в наше приложение.
В итоге я хотел получить что-то такое же простое, как коннект Redux:
connect(mapStateToProps, mapDispatchToProps)(Component)
Так как контекстов может быть много, я решил, что можно избавиться от лишнего кода и динамически передавать имя контекста, который мы будем использовать в конкретном месте, и получилось такое API:
withContext('My')(Component)
И всё! Остальное берёт на себя провайдер.
Так я это реализовал:
Реализация самого провайдера:
А вот как это удобно использовать:
И больше не надо писать action type, action creator и reducer.
Больше не надо импортировать всё это и подключать в коннекте.
Один импорт и один HOC, вся реализация описана в провайдере, и простые кейсы больше не требуют написания лишнего кода.
Пока я не реализовал поддержку нескольких контекстов для одного компонента, но это не сложно сделать при таком подходе.
Выводы
Я согласен, что далеко не все кейсы подходят для использования нового контекста, и есть много асинхронных вещей, которые пока реализуются только с помощью redux-saga или redux-thunk.
Но задумайтесь, насколько часто нужно просто фетчить данные, а потом их рендерить? У меня таких кейсов много. И если нужно получить данные больше чем в одном месте приложения, можно использовать новый контекст.
В своём примере я разобрал только работу с запросами данных, но контекст можно использовать и в других местах, таких как: авторизация, темы или общее состояние для разных частей приложения.
Вот как я вижу работу с состоянием приложения на текущий момент:
- данные нужны один раз в одном месте – setState
- данные нужны в нескольких местах – context
- сложные кейсы с асинхронными запросами – redux/saga/thunk/etc.
Гитхаб:
Рабочая демка: