React Context: создание глобального стора, используя useContext и useState
- суббота, 1 июля 2023 г. в 00:00:23
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пасибо