Прямое руководство по использованию React Hooks для глобального управления состоянием

Если вы разрабатываете приложение React, самый простой способ управления вашим глобальным состоянием и доступом к вашему бэкэнду - через React Hooks.

Несмотря на то, что с момента их выпуска в React 16.8 было написано много статей о хуках, на самом деле сложно найти руководство, которое бы хорошо объясняло, как использовать их в архитектуре вашего приложения.

Я предложу вам лучший масштабируемый подход к управлению совместным использованием данных в вашем приложении и работе с вашими backend API через хуки React, которые я использую во всех своих приложениях React.

Если вы уже знакомы с Redux, то мое решение должно быть очень похоже, с небольшими отличиями. Мы разработаем простое промежуточное программное обеспечение для глобальной области видимости, и будем управлять всеми вызовами API с помощью axios.

Заключительный API: Сохранение чистоты и простоты компонентов

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

Мы достигнем этой цели, используя контекстную ловушку, которая заставит наши компоненты выглядеть так:

Обратите внимание, что мы загружаем state и actions из обработчика контекста useContext. Таким образом, у нас есть доступ ко всему нашему состоянию и действиям в глобальном масштабе, и мы следуем принципу разделения проблем, поскольку хранение данных, манипулирование данными и представление данных обрабатываются отдельно.

Если какое-либо действие вызывает обновление глобального состояния, все компоненты сразу узнают об этом без необходимости добавления какой-либо дополнительной логики.

Не забывайте, что написание умного кода и построение умной архитектуры делают вас умными и сообразительными.

Пример: веб-приложение котировок

Чтобы было веселее, мы также создадим полное приложение, развертываемое в Netlify. Вы можете увидеть работающий конечный продукт здесь:
https://quotes.meet-martin.com/.

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

На нем есть цитаты даже из Уолта Диснея, и ни на одном плохом веб-сайте никогда не было бы цитат из Уолта Диснея, так что это должно быть довольно потрясающе.

Вы можете найти полный репозиторий GitHub здесь: https://github.com/MeetMartin/quotes.

Когда вы закончите эту статью, вы можете сами выяснить, как добавить дополнительные цитаты, создав запрос на вытягивание на GitHub и добавив их на веб-сайт.

Почему глобальное государственное управление?

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

Без разделения проблем ваши компоненты будут напрямую обращаться к API, например следующим:

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

Только злой архитектор программного обеспечения может заставить вас создать такой код.

Архитектура приложения

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

Как вы можете видеть в первом примере, компоненты React имеют доступ к глобальному состоянию из хранилища, которое является общим для всех компонентов и страниц.

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

Затем редукторы заботятся об обновлении хранилища, и все обновления состояния немедленно распространяются на все компоненты.

Что в коде приведет к этой файловой структуре этого каталога хранилища:

Разве это не красиво? Так много файлов и кода, которые мы должны проанализировать!

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

Начните с определения типов

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

Это первый файл, который вы обычно редактируете, когда хотите расширить свое приложение, добавив дополнительные действия.

В нашем приложении всего два типа: REQUEST_RANDOM_QUOTE и RECEIVE_RANDOM_QUOTE.

REQUEST_RANDOM_QUOTE - это то, как наша страница сообщает промежуточному программному обеспечению, что оно хочет, чтобы оно запрашивало случайную цитату, а RECEIVE_RANDOM_QUOTE - это то, как промежуточное программное обеспечение сообщает редуктору, что у него есть новая цитата.

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

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

Также очень важно выбрать хорошее соглашение об именах для ваших типов. В простых приложениях я обычно использовал REQUEST / RECEIVE, а в более сложных приложениях я использую CRUD (CREATE / READ / READ_ALL / UPDATE / DELETE).

Действия, доступные для компонентов

Действия - это то, как ваши компоненты будут запускать изменения и передавать данные промежуточному программному обеспечению и редуктору.

Нашему приложению потребуется только одно действие - requestRandomQuote, имя которого создает четкую связь с типом REQUEST_RANDOM_QUOTE.

Наша домашняя страница инициирует это действие, чтобы промежуточное ПО узнало, что ему нужна новая случайная цитата:

Без полезной нагрузки мы можем использовать это действие позже, просто вызвав actions.requestRandomQuote();.

Если бы мы хотели передать какие-то данные, мы также могли бы реализовать дополнительное действие, которое позволило бы нам вызвать actions.requestRandomQuoteFrom('Einstein');.

Любая полезная нагрузка, которую вы предоставляете, всегда доступна как для нашего промежуточного программного обеспечения, так и для нашего редуктора.

Обратите внимание, что нам не нужно создавать действие receiveRandomQuote в actions.js. Это потому, что мы ожидаем, что тип RECEIVE_RANDOM_QUOTE будет отправлен промежуточным программным обеспечением, а не каким-либо из наших компонентов.

В основном мы создаем действия как абстракцию, чтобы компоненты не работали напрямую с вызовами диспетчеризации.

Редуктор для обновления магазина

Наша система обновляет глобальное состояние в магазине, отправляя данные редуктору. Редуктор использует тип, который мы определили ранее в переключателе, чтобы обновить состояние.

Вы обнаружите, что с редукторами на самом деле довольно просто работать. Поверьте, я инженер.

Константа initialState имеет начальное состояние нашего приложения.

Мы используем console.debug, чтобы в вашем приложении вы могли видеть каждый раз, когда достигается редуктор. То же самое можно сделать и для нашего живого приложения на https://quotes.meet-martin.com/.

Редуктор достигается каждый раз, независимо от того, какие действия вы выполняете в промежуточном программном обеспечении. Из-за default на switch нам не нужно всегда обновлять состояние, если мы собираемся запускать только промежуточное ПО. Вот почему мы меняем состояние RECEIVE_RANDOM_QUOTE, но не предпринимаем никаких действий с REQUEST_RANDOM_QUOTE, который отсутствует в switch.

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

Как только новое состояние возвращается, все компоненты, считывающие любое из измененных значений, информируются об изменении состояния. Магия!

Каждый код всегда становится намного лучше, если вы добавляете немного магии функционального программирования.

Промежуточное ПО для связи с вашими API

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

Для реализации промежуточного программного обеспечения мы начнем использовать библиотеку функционального программирования @ 7urtle / lambda (www.7urtle.com). В функциональном программировании работа с внешним API считается побочным эффектом, и мы будем использовать монады, чтобы позаботиться об этих побочных эффектах, сохраняя при этом ваши функции чистыми.

Не волнуйтесь, если вы не понимаете монады или не слишком знакомы с функциональным программированием. Код будет настолько простым, что вы сможете использовать его в любом случае или заменить его своим собственным императивным решением. Однако, если вы хотите узнать больше о монадах, посмотрите Управление побочными эффектами с помощью монад в JavaScript.

Здесь начинается самое интересное.

Эффект фиктивной цитаты для получения случайной цитаты

Сейчас мы покажем, как будет выглядеть реальный запрос к функции Netlify. Теперь мы просто напишем простой код, который будет имитировать ситуацию выполнения запроса к серверу, но на самом деле он будет просто асинхронным, используя тайм-аут для задержки на одну секунду.

Нам понадобятся две функции: getRandomQuote, которая находит нам случайную цитату во входном массиве, и requestQuote, которая вернет наш эффект.

Наша функция requestQuote возвращает AsyncEffect - монаду с ленивым вычислением, которая обрабатывает асинхронные побочные эффекты. Он также возвращает другую монаду Maybe, которая может содержать результат случайной цитаты.

Реальная цитата - эффект получения случайной цитаты

В нашем сценарии это может быть запрос к функции Netlify. Функции Netlify - это упрощенная AWS Lambda, которая обрабатывает серверную часть.

Наш код выглядит так:

Вы можете видеть, что я добавил длинные комментарии с примерами, но сам код очень короткий.

Наш getQuoteFromNetlify - это функциональное частичное приложение общего postToFunction. postToFunction снова возвращает AsyncEffect, удерживая Может быть данных результата. Мы увидим, как они используются, когда перейдем к QuoteHook.js, который обеспечивает логику работы с нашими эффектами.

Об этих двух монадах важно помнить, что AsyncEffect похож на JavaScript Promise, но он не оценивается, пока вы не вызовете его триггер метода. Итак, как часть postToFunction, на самом деле еще не было выполнено ни одного вызова сервера, что сохраняет нашу функцию чистой.

Может быть - это монада, используемая, когда вы не можете быть уверены, что действительно получили значение. Maybe говорит, что он может иметь значение (равно Just) или не имеет значения (Nothing). Это дает нам довольно надежный и простой способ работы с возможными состояниями ошибок и проблемами.

Если вы используете функции Netlify, вы можете легко создать свой собственный эффект, также вызвав postToFunction.

export const myFunction = callToFunction('/your-function-path');

Если вы используете что-то еще, обе монады хорошо документированы в Документации @ 7urtle / lambda.

Крючки для вызова и обработки наших эффектов

Следующим шагом будет создание ловушки, которая будет вызывать и обрабатывать нашу функцию эффекта. Один и тот же код будет работать с любым из подходов, использующих фиктивную функцию или функцию Netlify.

Благодаря магии функционального программирования нам снова нужно всего несколько строк кода. Наш getQuote вызывает requestQuote или getQuoteFromNetlify из нашего эффекта. Он запускает возвращаемый AsyncEffect и регистрирует ошибки или вызывает dispatch для передачи данных в глобальное состояние через редуктор, который мы написали ранее.

AsyncEffect заботится о проблемах, связанных с axios вызовом, от случайной ошибки кода до ответа сервера 500 или 400 проблемами. А Maybe обрабатывается функцией возможно (строчная буква m), которая позволяет нам позаботиться о том, чтобы сервер не возвращал данные.

Никакой дополнительной логики здесь не требуется. Если вы хотите, вы также можете уведомить глобальное состояние об ошибке и использовать dispatch вместо console.error в своем коде.

Фактическое промежуточное программное обеспечение, связывающее все вместе

Фактический файл промежуточного программного обеспечения похож на наш редуктор. Он использует переключатель, который проверяет вызываемый тип и на основе этого вызывает ловушку, которую мы объявили ранее.

Промежуточное ПО всегда вызывается для каждого action (не dispatch), и если вам не нужно вызывать какой-либо перехватчик, вы можете просто игнорировать тип, и он успешно перейдет к редуктору.

Мы снова добавили console.debug в код в целях отладки, чтобы вы могли видеть, что происходит в приложении.

StoreContext для предоставления состояния и действий для компонентов

Последний файл, который нам нужно создать, - это StoreContext.js, который предоставляет нашему приложению глобальное управление состоянием.

Вам действительно не нужно редактировать этот файл, поэтому просто оставьте его как есть.

Он также содержит еще один console.debug, который регистрирует новое состояние каждый раз, когда оно изменяется, чтобы вам было легче его проверить.

Добавить глобальное управление состоянием в App.js

Это все для вашего приложения. Теперь вам просто нужно добавить свой StoreContext в App.js.

StoreProvider просто добавляется как компонент. Это делает его доступным для всего, что находится внутри него. В нашем простом приложении это просто HomePage, но в более крупном решении вы бы поместили его поверх React Router.

Главная страница

Наше одностраничное приложение использует HomePage для чтения текущей цитаты и вызывает requestRandomQuote действие с десятисекундными интервалами для обновления цитаты.

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

const { state, actions } = useContext(StoreContext);

Я обычно использую страницы как простые контроллеры, которые абстрагируют данные чтения от их представления в компонентах. Поэтому QuoteComponent заботится об отображении котировок, но не знает, что данные поступают из глобального состояния.

useEffect Перехватчик React используется для запуска setInterval добавления начала при первой загрузке HomePage. Он просто вызывает requestRandomQuote, и результат обрабатывается нашим редуктором. Состояние обновляется автоматически, и нам не нужен дополнительный код для обновления QuoteComponent.

Изменение цвета фона в зависимости от изменения котировки

Чтобы показать, что ваши компоненты могут выполнять дополнительные изменения в зависимости от изменения глобального состояния, я добавил компонент Background, который меняет фон на случайный цвет при каждом изменении цитаты.

Компоненты в стиле React используются мной для создания фактического фона div.

Мы используем хук useEffect React, который наблюдает state.quote, чтобы изменить цвет:

useEffect(
  () => setBackgroundColors(randomOf(colors))
, [state.quote]);

Затем текущий цвет фона сохраняется в локальном состоянии с помощью ловушки useState React, которая демонстрирует, как можно грамотно комбинировать как локальное, так и глобальное состояния.

const [ backgroundColors, setBackgroundColors ] = useState(randomOf(colors));

Заключение

Это почти все, что вам нужно сделать, чтобы все приложение работало.

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

Если что-то не работает, посмотрите полный репозиторий проекта на GitHub по адресу https://github.com/MeetMartin/quotes.

Если это не сработает, значит проблема где-то между клавиатурой и стулом, но не стесняйтесь оставлять комментарии. Я рад сделать все, что в моих силах, чтобы помочь или направить вас вперед.

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