С тех пор, как была объявлена функция React Hooks и API стал общедоступным, я думал о том, что это значит для Redux - хорошо известной библиотеки управления состоянием. Некоторое время раздаются голоса, что нам не нужен Redux, мы можем добиться того же с помощью React Context. Что ж, я думаю о Redux скорее как о шаблоне, чем о библиотеке. После дня игры с React Hooks я смог реализовать этот шаблон, используя context и hooks. Я покажу вам, как создать собственную оболочку Provider и обработчик useRedux. Да, и он полностью набран на TypeScript.
Провайдер
Сначала мы создаем Provider для нашего состояния и действий. Давайте создадим функцию с именем initRedux
, которая будет иметь 2 параметра: корневой редуктор и начальное состояние приложения, чтобы мы могли передать их ловушке useReducer.
const [state, dispatch] = useReducer(rootReducer, initialState)
Этот хук даст нам объект state и функцию dispatch. Эти два должны быть доступны во всем приложении. Лучший способ добиться этого - передать их вниз по дереву компонентов с помощью React Context, по одному контексту для каждого.
const StateContext = React.createContext(null) const DispatchContext = React.createContext(null)
Наш initRedux
метод создает Provider
, который уже содержит поставщиков для этих двух контекстов.
С помощью этого Provider
вы можете обернуть все свое приложение и сделать так, чтобы состояние вашего приложения и функция диспетчеризации были доступны в каждом компоненте. Но как мы можем использовать их в наших компонентах?
useRedux
Одно из больших преимуществ крючков - их способность сочинять. Вы можете сделать свой собственный крючок, используя другие крючки. Пока вы соблюдаете Правила хуков, вы можете играть с ними как хотите.
Как я уже сказал, нам нужен доступ к состоянию приложения и возможность отправлять наши действия. Чтобы было немного удобнее, я решил использовать интерфейс, очень похожий на тот, который используется функцией connect
из React-Redux. Если вы с ним не знакомы, обязательно проверьте, он очень полезен.
Итак, наш хук useRedux
будет принимать 2 аргумента с именами extractState
и actionMap
(параллельно с mapStateToProps
и mapDispatchToProps
).
Сначала мы извлекаем оба контекста.
const appState = React.useContext(StateContext) const dispatch = React.useContext(DispatchContext)
Конечно, мы не хотим возвращать все состояние приложения, но вместо этого мы используем функцию extractState
от аргументов до… ну, извлекаем желаемую часть состояния.
const stateExtract = extractState(appState)
Эта часть была легкой. Потребитель useRedux
уже предоставил функцию, которая принимает состояние приложения в качестве аргумента и выбирает только релевантные значения. Теперь нам нужно взять аргумент actionMap
и выполнить некоторую магию сопоставления. Предполагая, что actionMap
- это объект с формой {key: myActionCreator}
, давайте свяжем dispatch
функцию с этими создателями действий.
Код выполняет итерацию по ключам в объекте actionMap
и заменяет его значения (создателей действий) функциями, которые напрямую отправляют действия, возвращаемые этими создателями действий. С помощью одной простой модификации мы можем также отправлять преобразователи. Просто добавьте одно условие:
const fn = (...args) => { const action = actionCreator(...args) if (typeof action === 'function') { action(dispatch, () => appState) } else { dispatch(action) } }
Теперь все, что нам нужно сделать, это вернуть stateExtract
и actions
из нашего настраиваемого хука. Полная initRedux
функция выглядит так.
Примерно в 40 строках кода вы можете создать свой собственный хук Provider и useRedux. Я так и сделал, набрал все это с помощью TypeScript и опубликовал как redoox
пакет в npm. Вы можете попробовать и поиграть с этим, если хотите, но имейте в виду, что это всего лишь эксперимент, и он определенно не подходит для производства!
Уже существует множество пакетов, пытающихся добиться того же. Есть один даже из инкубатора Facebook. Я думаю, было бы неплохо, если бы не было множества пакетов, пытающихся решить одну и ту же проблему с немного другим API. Если вы знаете об одном, у которого есть шанс стать официальным, дайте мне знать в комментариях. Я с радостью помогу его реализовать.
Теперь давайте посмотрим, насколько хорошими могут быть возможности разработчика благодаря типизированному интерфейсу.
Как это работает?
Использование очень простое. В качестве примера я создал простое приложение, которое загружает пользователей из REST API, отображает их, и вы можете увеличивать / уменьшать их возраст.
- Создать состояние приложения
2. Реализация создателей действий
3. Установите редуктор. Я использую библиотеку immer, чтобы гарантировать неизменность. Обратите внимание, как вы вряд ли можете ошибиться при доступе к полезной нагрузке действия.
4. Селекторы орудий. Я использую библиотеку повторного выбора.
5. Создайте магазин.
6. И, наконец, используйте крючок в компоненте. Если все интерфейсы набраны правильно, вы всегда будете знать, какую часть состояния вы извлекли и какие действия доступны.
Вы можете найти полный пример здесь.
Просто клонируйте репозиторий, затем yarn
, yarn build
и yarn start
.
На протяжении своей карьеры разработчика программного обеспечения я сталкивался с множеством различных шаблонов управления состоянием, а не только для React / JavaScript. Пока что мне больше всего нравится Redux. Это предсказуемо, легко обслуживается и легко тестируется. Это те атрибуты, которые действительно важны для меня в отношении уровня данных приложения. Было бы здорово, если бы мы могли заставить его работать с React Hooks в большем масштабе.
Спасибо за прочтение. Если у вас есть вопрос или интересная идея, поделитесь, пожалуйста, в разделе комментариев.