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

от автора

Что такое 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 = new UseCreateAppContext(props);   return <Context.Provider value={context}>{children}</Context.Provider>; };

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

Вообще есть два способа организовать функцию, создающую объект-хранилище. Это либо использовать функцию-конструктор или функцию-генератор. Преимуществом использования функции-конструктора для создании стора является возможность сразу записывать в this-контекст поля, которые мы хотим использовать глобально, в месте их объявления. В то время как, если использовать функцию-генератор — т.е ту, которая явно возвращает объект — возвращаемые поля приходится отдельно дописывать в return в конце функции, а при больших сторах это становиться не удобно. Поэтому 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');   this.test = test;     this.toggleTest = useCallback(() => {     setTest(_test => (_test === 'Hi' ? 'You are awesome' : 'Hi'));   }); }

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

<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пасибо


ссылка на оригинал статьи https://habr.com/ru/articles/745162/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *