Эта статья посвящена созданию библиотеки для глобального управления состоянием на основе перехватчиков реакции. Вы можете использовать хуки для хранения и обновления глобального состояния вашего приложения React и даже рефакторинга существующего приложения из Redux в чистые реакции и хуки. Моя предыдущая статья была о таком рефакторинге, советую сначала прочитать ее. Также полный исходный код и документация для этой библиотеки управления состоянием доступны на Github и npm.

Постановка задачи

Перехватчики React позволяют эффективно, быстро и согласованно управлять состоянием некоторых компонентов. Вы можете делать практически все, что угодно, с состоянием компонента, обновлять его по событиям и организовывать свой код с помощью настраиваемых хуков.

Представьте, что вы хотите получить данные о текущем пользователе с помощью настраиваемого хука, вот код, который у вас будет для этого:

Этот хук отлично работает, за одним исключением - он перезагружает данные из API каждый раз, когда их использует новый компонент. Хуки не обмениваются данными - все данные изолированы от одного компонента. Вы можете использовать контекст, чтобы гарантировать, что данные загружаются один раз, но контекст не знает, когда данные действительно нужны, и требует некоторого шаблонного кода.

В этой статье мы разработаем библиотеку, которая превратит обычный пользовательский хук в пользовательский хук синглтона. Такой хук, использованный один раз, навсегда останется смонтированным внутри дополнительного компонента (например, поставщика контекста). Кроме того, ловушка никогда не будет смонтирована, если она не вызывается, это позволяет вам иметь много одноэлементных хуков в вашем проекте, и будут работать только соответствующие хуки. Данные, возвращенные из настраиваемой ловушки, будут распространяться на все компоненты, потребляющие ловушку, точно так же, как контекст реакции. Вот как в результате будет выглядеть создание одноэлементного хука:

Больше примеров и сценариев использования вы можете найти в разделе примеров в документации. В этой статье мы сосредоточимся на внутренней реализации.

Крючки высшего порядка

Первая идея, которую нам нужно принять, заключается в том, что singletonHook - это функция, которая принимает ловушку React и возвращает другую ловушку React, следовательно, это ловушка более высокого порядка. идея похожа на Компоненты высшего порядка, но вместо того, чтобы обернуть весь компонент, мы просто оборачиваем крючок. Этот метод полезен для отслеживания вызовов для перехвата, выполнения побочных эффектов при использовании перехватчика или для полной замены тела перехватчика. Вот простой пример того, как вы можете использовать HOH для печати сообщения, когда ловушка используется в первый раз и когда значение ловушки изменяется для любого компонента:

singletonHook ленивый

Теперь мы готовы приступить к singletonHook реализации. Реализация разделена на две части. singletonHook зависит от функции addHook. addHook принимает начальное значение, исходное тело ловушки и обратный вызов, которые будут вызываться при изменении состояния ловушки. Вся магия с установкой и поддержанием состояния хука происходит внутри addHook. singletonHook в основном заботится только о том, чтобы быть ленивым - ловушку не следует вызывать, если она не используется. Чтобы отследить это, мы используем переменнуюmounted, которая по умолчанию установлена ​​в false, а во время выполнения ловушки мы проверяем, равно ли она false. Также singletonHook поддерживает последнее известное состояние ловушки. Он инициализируется initValue и обновляется при каждом обновлении ловушки. это требуется для новых потребителей: когда новый компонент начинает использовать ловушку, он может уже иметь состояние, отличное от начального состояния. См. Код и комментарии ниже для получения дополнительных сведений о реализации:

Этот код страдает одним недостатком, который можно исправить: обновление потребителей не пакетируется, и нет гарантии заказа - обновление может быть вызвано для компонента более низкого уровня, когда этот компонент должен быть размонтирован компонентом более высокого уровня. В некоторых случаях это может привести к ошибкам. Например, разработчик может ожидать, что компонент никогда не будет отображаться в определенном состоянии. Эту проблему можно решить с помощью unstable_batchedUpdates, см. Полный исходный код, чтобы узнать, как именно он используется.

Еще один недостаток - пользователь должен явно передавать initValue. Было бы неплохо, если бы мы могли вычислить значение инициализации из хука, но я не нашел хорошего способа сделать это без фактического монтирования хука.

addHook - контейнер с крючками

addHook выделяет компонент и вызывает переданный обратный вызов при каждом изменении состояния компонента. Под капотом этот метод полагается на компонент React под названием SingletonHooksContainer. Этот компонент следует смонтировать до того, как будет использован какой-либо крючок, и это отдельная история о том, как происходит монтаж. Но при наличии SingletonHooksContainer мы можем просто добавить ловушку в его состояние. Затем каждый обработчик передается в отдельный компонент SingleItemContainer . Тело хука вызывается на этапе отрисовки SingleItemContainer. И если значение, возвращаемое обработчиком, изменяется, вызывается обратный вызов обновления. Вот минимальная реализация:

Эта реализация лишена некоторого кода проверки и монтирования, но все еще работает при условии, что SingletonHooksContainer смонтирован заранее. Пожалуйста, загляните в исходный код для получения полной информации о реализации, в том числе о том, как автоматически монтируется SingletonHooksContainer.

Что дальше?

Было бы неплохо найти способ избежать передачи начального значения в singletonHook, например, предсказать, что ловушка будет вызвана в следующем рендере, и хук рендеринга перед его использованием. Но пока - от пользователя требуется передать начальное значение. Кроме того, мне интересно, какое сообщество может найти ошибки и какие запросы могут иметь библиотека. Не стесняйтесь пользоваться библиотекой.