Как стать автором
Обновить

React Context: создание глобального стора, используя useContext и useState

Время на прочтение4 мин
Количество просмотров22K

Что такое react-контекст?

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

Интерфейс react-контекста состоит из метода createContext, компонента Context.Provider и хука useContext. 

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

Для чего использовать контекст?

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

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

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

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

Давайте на примере создания стора, посмотрим как использовать react-контекст.

Создание провайдера

Для того чтобы создать хранилище - в корне проекта создадим папку contexts. 

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

В нашем примере иерархия папок хранилища будет такая:

/contexts

  /AppContext

    AppContextProvider.jsx

    AppContext.js

Провайдер мы не меняем и после объявления будем только импортировать в jsx. А работать мы будем с контекстным хранилищем - добавлять в него общие методы и переменные.

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

Итак, объявим Context:

const Context = React.createContext(null);

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

Давайте объявим провайдер и назовем его AppContextProvider:

export const AppContextProvider = ({ children, ...props }) => {
  const context = useCreateAppContext(props);
  return <Context.Provider value={context}>{children}</Context.Provider>;
};

Здесь функция useCreateAppContext создает объект-хранилище.

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

Обратите внимание, что объект, возвращаемый useCreateAppContext, мы будем использовать в компонентах не на прямую, но получать его через useContext. Так величины контекстного хранилища будут находиться в памяти при ререндерах.

Так для получения контекстного хранилища, нужно использовать хук useContext с этим контекстом.

Давайте создадим в провайдере кастомный хук useAppContext, чтобы не экспортировать контекст и не передавать его каждый раз параметром из компонентов, в которых мы будем использовать стор:

export function useAppContext() {
  const context = React.useContext(Context);
  if (!context) throw new Error('Use app context within provider!');
  return context;
}

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

Последним для создания стора осталось объявить саму функцию useCreateAppContext, возвращающую объект который мы будем хранить в Provider value.

Исходные значения props - это те значения, которые получит компонент AppContextProvider.

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

Дальше содержимое стора можно наполнить чем угодно. 

Давайте объявим такой стор:

export const useCreateAppContext = function(props) {
  const [test, setTest] = useState(props.test || 'Hello world');
 
  const toggleTest = useCallback(() => {
    setTest(_test => (_test === 'Hi' ? 'You are awesome' : 'Hi'));
  });

  return {
    test,
    toggleTest,
  };
}

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

<AppContextProvider>
  <MyComponent />
</AppContextProvider>

Мы можем задать исходное значение переменной test:

<AppContextProvider test={‘Hello’}>
  <MyComponent />
</AppContextProvider>

Затем, в компоненте MyComponent вызвать useAppContext:

const appContext = useAppContext();

Так как поля в контекстном хранилище, как и само хранилище, обернуты в хуки, то мы спокойно можем воспользоваться деструктуризацией, не боясь потерять контекст:

const {test, toggleTest} = useAppContext();

И затем обращаться к нужным переменным:

console.log(test);
toggleTest();

При изменении переменной test приведет к ререндеру компонента, тк в сторе она храниться в хуке useState.

Собственно, вот и вся магия. 

Теперь содержимое useCreateAppContext можно менять на свое усмотрение и обращаться к нему глобально.

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

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

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

Почитать подробнее про контекст можно в документации react.

Cпасибо

** Примечание:

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

Теги:
Хабы:
Всего голосов 6: ↑3 и ↓30
Комментарии11

Публикации

Истории

Работа

Ближайшие события

27 августа – 7 октября
Премия digital-кейсов «Проксима»
МоскваОнлайн
28 – 29 сентября
Конференция E-CODE
МоскваОнлайн
28 сентября – 5 октября
О! Хакатон
Онлайн
30 сентября – 1 октября
Конференция фронтенд-разработчиков FrontendConf 2024
МоскваОнлайн
3 – 18 октября
Kokoc Hackathon 2024
Онлайн
7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн