С тех пор, как была объявлена ​​функция 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, отображает их, и вы можете увеличивать / уменьшать их возраст.

  1. Создать состояние приложения

2. Реализация создателей действий

3. Установите редуктор. Я использую библиотеку immer, чтобы гарантировать неизменность. Обратите внимание, как вы вряд ли можете ошибиться при доступе к полезной нагрузке действия.

4. Селекторы орудий. Я использую библиотеку повторного выбора.

5. Создайте магазин.

6. И, наконец, используйте крючок в компоненте. Если все интерфейсы набраны правильно, вы всегда будете знать, какую часть состояния вы извлекли и какие действия доступны.

Вы можете найти полный пример здесь.
Просто клонируйте репозиторий, затем yarn, yarn build и yarn start.

На протяжении своей карьеры разработчика программного обеспечения я сталкивался с множеством различных шаблонов управления состоянием, а не только для React / JavaScript. Пока что мне больше всего нравится Redux. Это предсказуемо, легко обслуживается и легко тестируется. Это те атрибуты, которые действительно важны для меня в отношении уровня данных приложения. Было бы здорово, если бы мы могли заставить его работать с React Hooks в большем масштабе.

Спасибо за прочтение. Если у вас есть вопрос или интересная идея, поделитесь, пожалуйста, в разделе комментариев.