Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Контекст (Context API) JS: React

Передача данных через пропсы вниз по иерархии компонентов — это немного многословный, но простой механизм. Всегда видно, откуда пришли данные и как они попали внутрь, а компоненты легко переиспользовать, так как они зависят только от входных данных. Но бывают ситуации, когда передача пропсов не вписывается в то, как работает код.

Возьмем для примера текущего пользователя. Часто данные пользователя нужны одновременно в разных частях страницы, причем в очень глубоких компонентах. Для этого придется передавать пользователя буквально по всей иерархии: даже там, где он не нужен компоненту. Единственная цель такой передачи — прокинуть данные до места назначения, пройдя по пути все промежуточные компоненты. Получается, что множество компонентов никак не используют пользователя, они просто передают их дальше по цепочке. В нашей ситуации данные пользователя глобальные. Они нужны сразу многим компонентам на разных уровнях иерархии. Для таких задач в React существует обходной путь.

Context API — механизм, позволяющий сделать глобальные данные доступными из любого компонента напрямую, без прокидывания пропсов. Его использование сводится к трем шагам:

  1. Создание контекста:

    // В параметр передается значение по умолчанию
    // Здесь передаем пустой объект, потому что пользователя еще нет,
    // но он будет (и будет объектом)
    // Контекст может хранить только одно значение
    // Имя контекста выбирается исходя из того, какие внутри хранятся данные
    const UserContext = React.createContext({});
    
  2. Передача данных в контекст. Работает так: оборачиваем нужные компоненты в компонент контекста <UserContext.Provider> и передаем туда нужные данные в проп value:

    // Контекст будет доступен только внутри тех компонентов, которые он оборачивает
    // и в тех, что вложены в данные компоненты
    // currentUser — данные текущего пользователя
    <UserContext.Provider value={currentUser}>
      <App />
    </UserContext.Provider>
    
  3. Получение данных из контекста. В компоненте, где нужны данные, нужно указать тип контекста с помощью статического свойства contextType. Реакт ищет ближайший провайдер этого контекста и берет из него значение. Поиск провайдера происходит вверх по дереву компонентов. Значение контекста будет доступно в this.context:

    import UserContext from '...';
    
    // Любой компонент внутри блока <UserContext.Provider>
    class InnerComponent extends React.Component {
    
      // Определяем тип контекста
      static contextType = UserContext;
      render() {
        // Получаем доступ к контексту через this.context
        return <Profile user={this.context} />;
      }
    }
    

Еще один пример, где несколько компонентов используют данные из контекста:

// Создаем контекст
const CompanyContext = React.createContext({});

// Компонент адреса компании
class CompanyAddressComponent extends React.Component {
  // Компонент использует контекст
  static contextType = CompanyContext;

  render() {
    // Извлекаем данные из контекста
    const { context } = this;
    const { address } = context;
    return (
      <>
        {address.street}
        <br />
        {address.city}, {address.post}
        <br />
        {address.country}
      </>
    );
  }
}

// Другой компонент отрисовывает название компании
class CompanyNameComponent extends React.Component {
  // Оба компонента используют один контекст
  static contextType = CompanyContext;

  render() {
    const { context } = this;
    const { name } = context;
    return <>
      {name}
    </>;
  }
}

class App extends React.Component {
  render() {
    // Компоненты могут быть вложены на любой глубине
    return (
      <>
        <CompanyNameComponent />
        <br />
        <CompanyAddressComponent />
      </>
    );
  }
}

const company = {
  name: 'Google',
  address: {
    street: '100 Bay View Drive',
    post: 'CA 94043',
    city: 'Mountain View',
    country: 'USA'
  }
};

const dom = (
  <CompanyContext.Provider value={company}>
    <App />
  </CompanyContext.Provider>
);

const mountNode = document.getElementById('react-root');
const root = ReactDOM.createRoot(mountNode);
root.render(dom);

В отличие от пропсов, изменение данных в контексте не приводит к перерисовке по умолчанию. Идеально, когда данные в контексте используются только для чтения. Изменяемые данные лучше хранить внутри состояния компонентов. Однако, если очень нужно, то реагировать на изменение контекста возможно, об этом подробнее можно прочитать в документации. В прикладном коде такая возможность используется редко, но на ней основаны разнообразные библиотеки.

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

import React from 'react';
import ReactDOM from 'react-dom/client';

// Создаем контекст компании
const CompanyContext = React.createContext({
  companies: [],
  currentCompany: {},
  setCompany: () => {},
});

// Массив компаний для переключения
const companies = [
  {
    id: 1,
    name: 'Google',
    address: {
      street: '1600 Amphitheatre Parkway',
      city: 'Mountain View',
      post: 'CA 94043',
      country: 'USA',
    },
  },
  {
    id: 2,
    name: 'Microsoft',
    address: {
      street: 'One Microsoft Way',
      city: 'Redmond',
      post: 'WA 98052',
      country: 'USA',
    },
  },
];

// Компонент для отображения названия компании
class CompanyNameComponent extends React.Component {
  static contextType = CompanyContext;
  render() {
    const { currentCompany } = this.context;
    return <h1>{currentCompany.name}</h1>;
  }
}

// Компонент для отображения адреса компании
class CompanyAddressComponent extends React.Component {
  static contextType = CompanyContext;
  render() {
    const { currentCompany } = this.context;
    const { street, city, post, country } = currentCompany.address;
    return (
      <p>
        {street}
        <br />
        {city}, {post}
        <br />
        {country}
      </p>
    );
  }
}

// Компонент для переключения компании
class CompanySwitcher extends React.Component {
  static contextType = CompanyContext;

  render() {
    const { companies, setCompany, currentCompany } = this.context;
    return (
      <div>
        <h3>Switch Company:</h3>
        {companies.map((company) => (
          <button
            key={company.id}
            onClick={() => setCompany(company)}
            disabled={company.name === currentCompany.name}
          >
            {company.name}
          </button>
        ))}
      </div>
    );
  }
}

// Главный компонент приложения
class App extends React.Component {
  constructor(props) {
    super(props);
    // Изначально выбираем первую компанию
    this.state = { currentCompany: companies[0] };
  }

  setCompany = (company) => {
    // Меняем компанию в состоянии
    this.setState({ currentCompany: company });
  };

  render() {
    const { currentCompany } = this.state;
    return (
      <CompanyContext.Provider
        value={{ companies, currentCompany, setCompany: this.setCompany }}
      >
        <CompanyNameComponent />
        <CompanyAddressComponent />
        <CompanySwitcher />
      </CompanyContext.Provider>
    );
  }
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

Разберем пример:

  1. Мы создали контекст CompanyContext, который хранит информацию о нескольких компаниях, текущую выбранную компанию и функцию для изменения компании.
  2. В компоненте <App/> определено состояние с текущей компанией и метод для её изменения.
  3. Компоненты <CompanyNameComponent/> и <CompanyAddressComponent/> получают данные из контекста и отображают название и адрес текущей компании.
  4. <CompanySwitcher/> позволяет переключать между компаниями, обновляя состояние и тем самым меняя отображаемые данные на странице.

При изменении данных в контексте компоненты, которые зависят от этих данных, будут перерисованы. Это происходит благодаря тому, что состояние, хранящее данные контекста, изменяется в компоненте-родителе (<App/>), и новое значение контекста передается вниз через провайдер. Это удобно для таких задач, где изменение состояния напрямую влияет на всё приложение (например переключение темы, языка, пользователя и тд).


Дополнительные материалы

  1. Context API

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
Осваивайте разработку веб-страниц, оживляйте дизайн макетов, публикуйте сайты и приложения. Отслеживайте ошибки в интерфейсе и устраняйте их
10 месяцев
с нуля
Старт 7 ноября
профессия
Обучитесь разработке визуальной части сайта — фронтенда, а также реализации серверной — бэкенда. Освойте HTML, CSS, JavaScript
16 месяцев
с нуля
Старт 7 ноября

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»