Использование Context для обмена состоянием между компонентами

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

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

Настройте наше мышление

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

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

Вариант 1 — усложнить ситуацию

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

Когда пользователь помечает уведомления как прочитанные, запускается другое действие отправки, которое отправляет запрос POST на серверную часть, и в случае успеха отправляется другое действие, которое удаляет этот конкретный элемент из нашего хранилища данных… это слишком сложно!!

Вариант 2 — Сделайте свою жизнь проще

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

На самом деле, его, вероятно, вообще не нужно хранить в состоянии! Что, если бы мы использовали простой пользовательский хук, который отвечал за получение и возврат уведомлений, а также за отправку запроса markAsRead? Весь этот сложный спагетти-код управления состоянием, вероятно, можно было бы очистить, абстрагировать от компонента и использовать с помощью всего одной строки в компоненте!

(Для более подробного объяснения того, как использовать эту технику, ознакомьтесь с Управление асинхронным состоянием с помощью React-Query)

Скорректировав наше мышление, мы получим очень чистый компонент <NotificationsDrawer /> и пользовательский хук, который гораздо более удобочитаем, чем реализация полноценной системы управления состоянием. Его также будет намного проще поддерживать на случай, если нам понадобится внести изменения в то, как/что мы получаем в будущем.

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

Использование контекста для совместного использования состояния компонентами

Контекстный API React предоставляет нам способ доступа к состоянию глубоко в дереве компонентов без необходимости передавать реквизиты каждому отдельному компоненту на этом пути (известное как «детализация реквизита»).

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

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

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

Без Context API это простое требование было бы оченьсложно реализовать. Нам нужно было либо пропустить команду через реквизиты по всему дереву компонентов («бурение реквизитов»), либо нам пришлось бы сдаться и использовать тяжелую стороннюю библиотеку управления состоянием (фу!).

Вот как может выглядеть наша реализация этой функции, если мы используем контекст…

Это решает все! Что именно хранится в контексте? У нас есть allTeams, который представляет собой массив всех объектов команды, возвращенных из бэкенда. Это можно использовать для создания параметров для выбранного компонента, который мы поместим в верхнюю строку меню, чтобы пользователь мог изменить выбранную команду.

У нас также есть team, которую мы будем использовать в качестве команды, в данный момент выбранной пользователем, и setTeam, которую мы можем вызвать, когда пользователь изменит свой выбор (это то, что будет вызываться в onChange команды команда выбирает компонент).

Использование крючка

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

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

Хотя созданный нами хук — это то, что мы будем использовать чаще всего, Provider не менее важен!

Что с провайдером?

Мы можем использовать наш хук useTeamContext только тогда, когда мы находимся в дочернем компоненте Provider. Это в то время как наш компонент <TeamsProvider /> использовал children в качестве реквизита!

Если мы ожидаем, что наш контекстный хук будет использоваться во всем приложении, мы должны поместить компонент TeamsProvider очень высоко в дереве компонентов. На самом деле очень часто можно увидеть провайдеров в компоненте <App />, поскольку это фактически означает, что все ваше приложение будет иметь доступ к тому же контексту, который он предоставляет.

Заключение

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

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

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