логотип PurpleSchool
логотип PurpleSchool

Управление состоянием в React через Context

Автор

Олег Марков

Введение

Если вы уже работали с React, вы наверняка сталкивались с необходимостью передавать данные между компонентами. Часто это приводит к так называемому "prop drilling" — когда пропсы передаются через многоуровневую иерархию. Это становится неудобно и затрудняет сопровождение кода. Что же делать, если хочется держать часть состояния доступной для нескольких, возможно, удалённых друг от друга компонентов?

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

Что такое Context в React и зачем он нужен

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

Когда уместно использовать Context

React Context отлично подходит для следующих задач:

  • Передача параметров темы оформления (светлая/тёмная)
  • Передача информации о текущем пользователе
  • Локализация (language strings)
  • Любое другое "глобальное" состояние, связанное с деревом компонентов

Однако Context не предназначен для полноценного управления состоянием сложных приложений, где понадобится управление множеством связанных состояний и бизнес-логики. Для этого лучше использовать такие инструменты, как Redux или Zustand.

Как работает Context API в React

Создание контекста

Сначала необходимо создать контекст с помощью функции React.createContext. Посмотрите пример:

import React from 'react'

// Создаём новый контекст с базовым значением null
const MyContext = React.createContext(null)

Это создаёт объект Context, в котором есть два основных элемента:

  • Provider — компонент, через который предоставляется значение контекста
  • Consumer — компонент, который подписывается на изменения значения

Оборачивание компонентов контекстом (Provider)

Любой компонент дочерний к Provider сможет получить значение, передаваемое в этот Provider.

<MyContext.Provider value={someValue}>
  <ChildComponent />
</MyContext.Provider>

Всё, что находится внутри этого Provider, будет иметь доступ к someValue через Consumer или через хук, о которых поговорим ниже.

Получение значения контекста

Есть два способа получить данные из контекста:

  • Через Consumer-компонент (старый метод)
  • Через хук useContext (рекомендуется, начиная с React 16.8)

Пример с Consumer-компонентом

<MyContext.Consumer>
  {value => (
    <div>Текущее значение: {value}</div>
  )}
</MyContext.Consumer>

Этот подход менее удобен из-за вложенности функций.

Использование useContext

В современных приложениях использование хука useContext предпочтительнее:

import React, { useContext } from 'react'

const value = useContext(MyContext)
// Теперь value — это значение из ближайшего Provider выше по дереву компонентов

Это очень просто и удобно. Обратите внимание: если значение в Provider изменится, все компоненты, использующие useContext, автоматически обновятся.

Context в React предоставляет способ передачи данных между компонентами без необходимости прокидывать props через каждый уровень дерева компонентов. Это полезно для передачи глобальных данных, таких как тема оформления, язык или информация об аутентификации. Если вы хотите научиться управлять состоянием в React с помощью Context и узнаете о его преимуществах — приходите на наш большой курс Основы React, React Router и Redux Toolkit. На курсе 177 уроков и 17 упражнений, AI-тренажеры для безлимитной практики с кодом и задачами 24/7, решение задач с живым ревью наставника, еженедельные встречи с менторами.

Пример использования Context в приложении React

Покажу вам реальный пример. Допустим, вы делаете переключатель темы (светлая/тёмная).

1. Создаём контекст и провайдер

import React, { createContext, useState } from 'react'

// Создаём контекст с начальными значениями
export const ThemeContext = createContext({
  theme: 'light', // значение по умолчанию
  toggleTheme: () => {}
})

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light')

  // Функция для переключения темы
  const toggleTheme = () => {
    setTheme(prevTheme => (prevTheme === 'light' ? 'dark' : 'light'))
  }

  // Оборачиваем дочерние компоненты
  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  )
}

2. Использование контекста в дочерних компонентах

import React, { useContext } from 'react'
import { ThemeContext } from './ThemeProvider'

function ThemeTogglerButton() {
  // useContext отдаёт объект с темой и функцией переключения
  const { theme, toggleTheme } = useContext(ThemeContext)

  return (
    <button onClick={toggleTheme}>
      Текущая тема: {theme}. Сменить!
    </button>
  )
}

3. Оборачивание всего приложения провайдером

import React from 'react'
import { ThemeProvider } from './ThemeProvider'
import ThemeTogglerButton from './ThemeTogglerButton'

function App() {
  return (
    <ThemeProvider>
      <ThemeTogglerButton />
      {/* Другие дочерние компоненты */}
    </ThemeProvider>
  )
}

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

Особенности Context: когда стоит быть внимательнее

Повторные перерисовки

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

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

Мемоизация значений

Чтобы избежать лишних рендеров, полезно использовать useMemo для значения, передаваемого в Provider. Покажу, как это делается:

import React, { useState, useMemo } from 'react'

const MyContext = React.createContext(null)

function MyProvider({ children }) {
  const [count, setCount] = useState(0)

  // Мемоизируем объект контекста, чтобы его ссылка не менялась зря
  const value = useMemo(() => ({ count, setCount }), [count])

  return (
    <MyContext.Provider value={value}>
      {children}
    </MyContext.Provider>
  )
}

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

Вложенные и несколько Provider-ов

Вы можете использовать сразу несколько контекстов (например, Theme и User) и оборачивать компоненты в несколько провайдеров:

<ThemeContext.Provider value={themeValue}>
  <UserContext.Provider value={userValue}>
    <App />
  </UserContext.Provider>
</ThemeContext.Provider>

Также возможно создавать разные "поддеревья" с различными значениями контекста.

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

Вызов useContext возможен только внутри компонентов React или пользовательских хуков, которые вызываются в компонентах. Вне компонента (например, в обычном JS-модуле) прочитать значение контекста нельзя. Это важно помнить при архитектуре приложения.

Валидация значений контекста

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

import { useContext } from 'react'
import { ThemeContext } from './ThemeProvider'

function useSafeTheme() {
  const context = useContext(ThemeContext)
  if (!context) {
    throw new Error('useSafeTheme должен использоваться внутри ThemeProvider')
  }
  return context
}

Как лучше структурировать контексты

Одна задача — один контекст

Создавайте отдельный контекст для каждой логической группы состояния: тема, язык, пользователь, авторизация, настройки и т.д.

Комбинирование с хуками

Используйте React-хуки внутри Provider для управления состоянием. Так достигается модульность и повторное использование логики.

Плюсы и минусы Context API

Преимущества

  • Простота интеграции: нет сторонних библиотек
  • Нет необходимости внедрять большой глобальный стор
  • Отлично подходит для передачи неизменяемых или редко изменяемых настроек (например, темы/языка)

Недостатки

  • Не подходит для большого количества динамического состояния (например, для сложных бизнес-процессов)
  • Может вызывать лишние перерисовки при изменении значения
  • Не удобен для отладки, если используетсяповсеместно и хаотично

Практические советы

  • Избегайте "перенасыщения" контекстами: выделяйте только те сущности, которым нужен глобальный доступ.
  • Мемоизируйте value, отдаваемое через Provider, если туда входит объект или функция.
  • Не передавайте часто обновляемое состояние через Context без необходимости.
  • Не бойтесь создавать отдельный контекст для каждой логической сущности — это упростит поддержку.

Заключение

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

Context позволяет удобно управлять глобальным состоянием. Для создания сложных приложений требуется умение управлять состоянием всего приложения и организовывать навигацию. На курсе Основы React, React Router и Redux Toolkit вы освоите все необходимые инструменты. В первых 3 модулях уже доступно бесплатное содержание — начните погружаться в основы React уже сегодня.

Частозадаваемые технические вопросы по теме статьи и ответы на них

Как обновлять только часть значения контекста, не пересоздавая весь объект?

Если в value передаётся объект, и вы обновляете только одно его поле, useMemo поможет мемоизировать объект. Лучше использовать state с useReducer внутри Provider и передавать только нужные функции-обновления и значения потребителям.

Пример: jsx const [state, dispatch] = useReducer(reducer, initialState) const contextValue = useMemo(() => ({ state, dispatch }), [state])

Как протестировать компонент, использующий Context?

В тестах оборачивайте компонент в нужный Provider и передавайте туда тестовое значение. Например, используя react-testing-library:

render(
  <MyContext.Provider value={testValue}>
    <ComponentUnderTest />
  </MyContext.Provider>
)

Можно ли использовать useContext в классовых компонентах?

В классовых компонентах нужно использовать Consumer-компонент или статическое поле contextType:

class MyComponent extends React.Component {
  static contextType = MyContext
  render() {
    return <div>{this.context}</div>
  }
}

Как сбросить значение контекста в дочернем компоненте?

В дочернем компоненте вызывайте функцию-обновление из value контекста, например, setValue или dispatch, если их экспортирует Provider.

Как организовать несколько контекстов с вложенными Provider?

Оборачивайте компоненты в нужные Provider-ы, порядок значения не имеет:

<FirstContext.Provider value={firstValue}>
  <SecondContext.Provider value={secondValue}>
    <Component />
  </SecondContext.Provider>
</FirstContext.Provider>

Это сделает каждое значение доступным через useContext в любом месте под соответствующим Provider.

Стрелочка влевоКак и зачем использовать React Hooks

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий