Зачем нужна React Context и когда используется
В библиотеке React используются разные способы передачи данных между компонентами приложения:
- props/пропсы (properties);
- The Context (React Context API);
- React-Redux/Redux и иные State Manager-ы.
У каждого способа есть плюсы и минусы. Ниже разберем, с чем возникают сложности и почему.
Проблема проп-вординга и решение через контекст
Программисты в Реакт работают с деревом компонентов. Основной способ — прокидывать пропсы «от родителя к ребенку» (from Parent to Child). К примеру, есть дерево:
Нам нужно передать to Child — нижнему компоненту — свойства (theme — цветовую тему, lang — язык или любые другие в соответствии с логикой приложения). На изображении этот компонент отмечен фиолетовым.
Чтобы пропс был получен, его должен передать родитель. Например, наш component получил props (к примеру, theme) от родителя и изменил цвет. И здесь видна первая проблема props. Нашу theme получает и второй дочерний компонент, хотя ему не нужны данные проперти. А подобных компонентов «внизу дерева» может быть 100 или 200. Получается, они засоряются пропсами, которые не используются.
Но есть и еще один нюанс. Что делать, если от нижнего цветного блока нужно передать theme соседнему (с которым наш блок не связан отношениями from Parent to Child)?
Передать theme в пропсах соседу не получится. Придется поднять их с нижнего центрального блока на верхнего родителя (to Parent) и от него уже спустить нужному компоненту (to Child).
Чтобы передать одно свойство, нам пришлось задействовать половину дерева. При этом нужны пропсы и theme были всего лишь одному компоненту. Сами же пропсы могут быть достаточно «тяжелыми» — например, выбранный язык, UI-тема (theme).
Это можно сравнить с ситуацией, когда в компании не налажены горизонтальные связи между отделами. Например, редактору нужно, чтобы дизайнеры писали во всех материалах GitVerse. Вместо того, чтобы пойти напрямую в соседний отдел с дизайнерами и сказать: «С сегодняшнего дня мы пишем GitVerse, а не The GITVERSE во всех маркетинговых материалах», редактору приходится подниматься к руководителю отдела контента, ему — к заместителю директора, заместителю — к гендиректору, гендиректору — к руководителю отдела дизайна, руководителю отдела дизайна — к дизайнеру UX/UI. Было бы здорово придумать механизм, чтобы передача данных осуществлялась без проблемы «проброса по уровням». Так и появился React Context.
Преимущества использования React Context
React Context API — интерфейс, позволяющий сохранить переменную или объект и использовать ее несколькими компонентами, не имеющими связи «родитель–ребенок» (from Parent to Child). Контекст же (контекстный стор) — это и есть та сохраненная величина.
Интерфейс react-контекста включает:
- компонент Context.Provider c value;
- метод createContext();
- хук useContext().
В React Context есть:
- провайдер (Provider — тот, кто передает контекст и value);
- получатель (Consumer — тот, кто получает контекст).
При таком подходе пропадают «лишние звенья», которые использовались только для передачи пропсов и нужных value на уровень выше. Появляются только Provider и Consumer (from Provider to Consumer).
Создание контекста в React: начальный этап
Пошаговая инструкция, что нужно сделать для создания Provider, контекста и потребителя:
- Создать контекст через createContext(). Сделать это можно как в файле провайдера, так и в отдельном файле (но потом импортировать в Provider).
- Обернуть родительский компонент, чтобы все дочерние имели доступ к контексту (to store). Вложенные в провайдера компоненты могут получить контекстный стор.
- Создать дочерние компоненты. Они будут потребителями. Для доступа к стору используется хук useContext (или обертка <ComponentContext.Consumer> </ComponentContext.Consumer>).
Писать код можно в среде разработки GIGA IDE Desktop или GIGA IDE Cloud. Умный AI-ассистент объяснит или задокументирует код и возьмет на себя часть рутинных задач разработчика.
Чтобы все заработало, создаем проект и файлы в папке src:
- main.tsx — точка входа, где будет отрисовываться компонент GitVerseApp;
- GitVerseContex.jsx — стор (контекст);
- GitVerseApp.jsx — Provider и его value;
- GitVerseChildComponent.jsx — дочерний компонент.
Структура проекта выглядит таким образом:
Использование метода createContext
В файле GitVerseContex.jsx объявляем Context.
Код в файле GitVerseContex.jsx (названия проектов и переменных разработчик может задавать самостоятельно) выглядит таким образом:
import { createContext } from 'react';
const GitVerseContex = createContext();
export default GitVerseContex;
Определение провайдера и потребителей контекста
Далее необходимо создать провайдера контекста. Им оборачивают все те компоненты, которые будут использовать глобальные переменные стора.
Объявляем Provider и называем его — например, GitVerseAppProvider.jsx. Код:
import './App.css';
import GitVerseContex from './GitVerseContex';
const GitVerseAppProvider = () => (
<GitVerseContex.Provider value="GitVerseParentPage">
<h1>Ура, вместе с Gitverse создали провайдера!</h1>
</GitVerseContex.Provider>
);
export default GitVerseAppProvider;
Создаем дочерний компонент GitVerseChildComponent.jsx, который делает return простой фразы. Код:
import { useContext } from 'react';
import GitVerseContex from './GitVerseContex';
const GitVerseChildComponent = () => {
const GitVersepageName = useContext(GitVerseContex);
return <p>Это дочерний компонент для the page {GitVersepageName}</p>;
};
export default GitVerseChildComponent;
Код с return пока не будет работать, ведь мы его не подключили. Необходимо перейти в GitVerseAppProvider и отрисовать в нем дочерний компонент. Код:
import './App.css';
import GitVerseContex from './GitVerseContex';
import GitVerseChildComponent from './GitVerseChildComponent';
const GitVerseAppProvider = () => (
<GitVerseContex.Provider value="GitVerseParentPage">
<h1>Ура, вместе с Gitverse создали провайдера!</h1>
<GitVerseChildComponent />
</GitVerseContex.Provider>
);
export default GitVerseAppProvider;
Пока контекстный стор пуст. Можно перейти в файл GitVerseContex и вывести в консоль содержимое GitVerseContex.
Добавляем console.log(GitVerseContex):
В результате можно убедиться, что объект действительно существует (список value ниже):
Стор нужно наполнить данными (theme, языковая версия, геолокация и другие в зависимости от логики приложения).
React Context API: как использовать контекст в приложениях
The React Context API — тема, которую на самом деле разработчики редко используют «как есть». Необходимо понимать, как работает контекст. Но для написания кода давно созданы специальные библиотеки, которые делают всю логику The React Context «под капотом». Например, Redux с его flux-подходом. На смену Редаксу пришли библиотеки Mobx и Zustand. Сегодня используют фреймворк The TanStack (поддерживает не только React, но и Solid, Vue, Angular, Svelte).
Применение хука useContext для доступа к данным
useContext — это hook (хук), который позволяет подписаться на контекст и считывать с него данные. С помощью useContext можно делать кастомные хуки.
Синтаксис:
const value = useContext(GitVerseThemeContext);
Пример кода с хуком для работы с UI-темой (the theme):
import { useContext } from 'react';
function GitVerseComponent() {
const theme = useContext(GitVerseThemeContext);
}
Управление глобальным состоянием с React Context и useState
useState — это вебхук, который позволяет добавить в компонент переменную состояния.
Синтаксис:
const [state, setState] = useState(initialState);
Пример кода с использованием useState для управления состоянием:
import { useState } from 'react';
function GitVerseComponent() {
const [id, setId] = useState(183);
const [projectName, setName] = useState('GigaCode');
const [tasks, setTasks] = useState(() => createTasks());
}
GigaCode объяснит, как работает useState в написанном выше коде:
Еще с помощью GigaCode можно создать более сложную функцию, в которой будет использовать useContext и useState, а затем возвращать (return) component:
Работа с контекстом на классовых компонентах
При работе с классовыми components (для тех, кто пишет на классах) код будет выглядеть иначе. Пошаговая инструкция — ниже.
Создать контекст:
const GitVerseContext = React.createContext(defaultValue);
Создать провайдера и передать в него value:
<GitVerseContext.Provider value={theme}>
Объявить Class.contextType и использовать методы классов (не забывать использовать this):
class GitVerseClass extends React.Component {
componentDidMount() {
let value = this.context;
}
componentDidUpdate() {
let value = this.context;
}
componentWillUnmount() {
let value = this.context;
}
render() {
let value = this.context;
}
}
GitVerseClass.contextType = GitVerseContext;
Назначить потребителя:
<GitVerseContext.Consumer>
{value => 'Здесь рендерится что-то с использованием контекста — the theme, value и т.д.'}
</GitVerseContext.Consumer>
Классовые компоненты с this считаются легаси-кодом. Но на отдельных проектах их по-прежнему можно встретить. С хуками все проще: достаточно использовать веб-хук useContext, чтобы получить доступ к стору. Не нужно оборачивать компонент в <ComponentContext.Consumer></ComponentContext.Consumer>.
Оптимизация производительности при использовании React Context
Частая проблема, которая бьет по производительности, — ререндеры компонентов (даже при использовании memo и useMemo). Как улучшить производительность?
Лучшие практики для эффективного использования контекста
- Значение, которое передается Провайдером в value, — это всегда новый объект, даже если обернуть функции в useCallback. Для оптимизации нужно их мемоизировать.
- Сами функции создают новые ссылки в памяти, и их также необходимо мемоизировать.
- При использовании useCallback массив зависимостей целесообразно оставлять пустым. Значение передаются через стрелочную функцию как параметр.
- При передаче value в Provider необходимо отделить значения и функции. Это позволит избежать перерендера.
- ReactContext не стоит использовать для хранения локального состояния.
- Контекст может стать причиной так называемого «Context Hell» с большим количеством values («елочка», «лестница»). В таких случаях целесообразно использовать State Managers или хотя бы писать хелперы.
- ReactContext нецелесообразно использовать, если нужно пробросить данные буквально на один-два уровня.
React Context vs Redux: когда выбирать контекст для управления состоянием
Redux — это библиотека для управления состоянием (стейт менеджер). Условно говоря, может быть ситуация, когда в зависимости проекта устанавливается library весом как 3 Реакта. Но при этом разработчики используют только 5–10% ее функциональности. Естественно, в таком случае возникает вопрос эффективности.
Но в случае с глобальным стором целесообразно использовать придуманное и протестированное решение — Redux.
Критерий сравнения | The React Context | The Redux |
Что такое | Способ передавать данные через дерево без необходимости пробрасывать их через уровни (официальная документация) | Паттерн проектирования и библиотека для управления, состоянием приложения |
Основные составляющие | The Provider (поставщик), The Consumer (получатель), The Context (стор) | Store, Actions (объекты для инициирования изменений) и Reducers (функции, отвечающие за обновления). Дополнительно появляютсяMiddleware (промежуточный слой) и некоторые другие. |
Работа с side effects | Отвечает только за рендеринг | Работает с побочными эффектами |
Для каких ситуаций подходит | Нужно прокинуть небольшую UI-тему, языковую версию и т.д. Подходят для пет-проектов и изучения, как работает Redux под капотом | Крупные проекты, где нужно много глобального стейта |
Нет удобной интеграции с асинхронными actions (нужно писать самостоятельно) через useEffect. Можно попробовать писать экшены самостоятельно | ||
Отслеживание, почему и как обновлятся приложение | Сложно. Нет четкой структуры action: разделения на reducers, заранее созданных экшенов и так далее. Логика может быть размазана по всему приложению | Проще |
Возможность асинхронного обновления | Затруднительны асинхронные обновления компонентов | Возможны асинхронные обновления компонентов |
Примеры использования React Context в реальных проектах
The ReactContext можно сравнить со своего рода «тоннелем» (pipe), через который прокидываются данные. Это не полноценный инструмент управления состоянием по сравнению с библиотеками и стейт-менеджерами. Его основное предназначение — избавиться от Props drilling, проблемы прокидывания пропсов и value между соседними компонентами.
Основные кейсы использования The ReactContext:
- тема/theme (переключение между светлым и темным режимами);
- регион и геолокация пользователя;
- иные данные, которые не требуют сложной логики и частого обновления.
Подводные камни и ограничения React Context
ReactContext запускает повторный рендеринг, и для устранения этой проблемы приходится использовать мемоизацию (которая помогает не всегда).