Полное руководство по React Context

Представленные советы будут полезны даже тем, кто еще никогда не работал с React. Возможности Context изучим на простых, пошаговых примерах.

Что такое React Context?

React Context позволяет без использования props’ов передавать и применять (потреблять) данные в любом компоненте приложения.

Другими словами, React Context упрощает обмен данными (состоянием) между компонентами приложений.

Когда нужен React Context?

React Context эффективен при передаче данных, используемых в любом компоненте приложения.

Возможные типы данных:

· Данные темы (например, темный или светлый режим);

· Пользовательские данные прошедшего аутентификацию пользователя;

· Зависящие от местоположения данные (например, язык или регион пользователя).

В React Context следует размещать данные, не требующие частого обновления.

Причина в том, что Context предназначен лишь для упрощения процесса использования данных. Он не является целостной системой управления состоянием.

Сontext можно рассматривать как эквивалент глобальных переменных для компонентов React.

Какие проблемы решает React Context?

React Context помогает отказаться от “пробрасывания” props’ов (props drilling).

Термин Props drilling означает передачу props’ов во вложенный компонент через другие компоненты, расположенные ниже родительского и сами в данный момент не нуждающиеся этих пропсах.

Рассмотрим пример props drilling. В этом приложении есть доступ к данным темы, которые мы хотим передать как prop всем компонентам приложения.

При этом прямые дочерние элементы App, такие как Header, также должны передавать с помощью props’ов вниз по иерархии данные темы (theme data).

export default function App({ theme }) {
return (
<>
<Header theme={theme} />
<Main theme={theme} />
<Sidebar theme={theme} />
<Footer theme={theme} />
</>
);
}
function Header({ theme }) {
return (
<>
<User theme={theme} />
<Login theme={theme} />
<Menu theme={theme} />
</>
);
}

В чем здесь проблема?

В том, что theme пробрасывается через несколько компонентов, в которых этот prop в данный момент вовсе не нужен.

В Header данные theme нужны лишь для того, чтобы передать их вниз дочернему компоненту. То есть, для User , Login и Menu было бы лучше напрямую получать данные theme.

Отказаться от пробрасывания пропсов позволяет React Context.

Как использовать React Context?

Context  —  это API, встроенное в React с версии 16.

Поэтому создавать и использовать Context можно посредством импорта React в созданные на его основе проекты.

4 шага использования React Context:

1. Создать Context, используя метод createContext.

2. Использовать поставщика созданного Context (context provider) в качестве оболочки дерева компонентов.

3. Поместить любое необходимое значение в context provider, используя prop value.

4. Прочесть это значение в любом компоненте с помощью context consumer.

Звучит несколько запутанно? Но здесь нет ничего сложного.

Вот простой пример. В рассмотренном ранее App передадим с помощью Context имя Reed и прочитаем его во вложенном компоненте User.

import React from 'react';

export const UserContext = React.createContext();

export default function App() {
  return (
    <UserContext.Provider value="Reed">
      <User />
    </UserContext.Provider>
  )
}

function User() {
  return (
    <UserContext.Consumer>
      {value => <h1>{value}</h1>} 
      {/* prints: Reed */}
    </UserContext.Consumer>
  )
}

Разберем этот код по шагам.

1. В компоненте App создаем Context с помощью React.create Context () и помещаем результат в переменную UserContext. Его практически всегда можно экспортировать (как сделано здесь), потому что ваш компонент будет в другом файле. Обратим внимание, при вызове React.createContext() можно передать начальное значение в prop value.

2. В компоненте App используем User Context. В данном случае User Context.Provider. Созданный Context  —  объект с двумя свойствами: Provider и Consumer. Они являются компонентами. Чтобы передать value вниз каждому компоненту App, используем в качестве оболочки компонент поставщика (в данном случае User).

3. В UserContext.Provider помещаем значение, которое хотим передать вниз по иерархии компонентов. Для этого устанавливаем его равным prop value. В данном случае это имя Reed.

4. Используем потребительский компонент в UserContext.ConsumerUser и везде, где хотим потреблять (использовать) представленные в контексте данные. Для использования переданного вниз значения применяем render props pattern. Это просто функция, которую потребительский компонент (consumer component) предоставляет нам в качестве prop. Данная функция возвращает используемое в дальнейшем value.

Что такое хук useContext?

В приведенном выше примере не очень понятным может быть шаблон рендеринга props’ов для потребления контекста.

В React версии 16.8 для этого есть другой способ с применением хуков (с помощью useContext hook).

Вместо использования render props можно передать весь объект-контекст в React.useContext() для потребления в верхней части компонента.

Пример использования хука useContext hook:

import React from 'react';

export const UserContext = React.createContext();

export default function App() {
  return (
    <UserContext.Provider value="Reed">
      <User />
    </UserContext.Provider>
  )
}

function User() {
  const value = React.useContext(UserContext);  
    
  return <h1>{value}</h1>;
}

Преимущество хука useContext в том, что он делает компоненты более компактными и позволяет создавать пользовательские хуки.

В зависимости от предпочитаемого шаблона (паттерна) можно использовать напрямую потребительский компонент или хук useContext.

Всегда ли нужен Context?

Ошибкой многих разработчиков является обращение к Context, когда им нужно передавать props’ы на несколько уровней вниз к компоненту.

Вот приложение с вложенным компонентом Avatar, которому требуется два реквизита username и avatarSrc из компонента App.

export default function App({ user }) {
  const { username, avatarSrc } = user;

  return (
    <main>
      <Navbar username={username} avatarSrc={avatarSrc} />
    </main>
  );
}

function Navbar({ username, avatarSrc }) {
  return (
    <nav>
      <Avatar username={username} avatarSrc={avatarSrc} />
    </nav>
  );
}

function Avatar({ username, avatarSrc }) {
  return <img src={avatarSrc} alt={username} />;
}

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

Как это сделать?

Вначале следует упорядочить компоненты вместо того, чтобы сразу обращаться к контексту для передачи props’ов.

Поскольку только самый верхний компонент App должен знать о компоненте Avatar, мы можем создать его непосредственно в App.

Это позволяет передавать один prop avatar вместо двух.

export default function App({ user }) {
  const { username, avatarSrc } = user;

  const avatar = <img src={avatarSrc} alt={username} />;

  return (
    <main>
      <Navbar avatar={avatar} />
    </main>
  );
}

function Navbar({ avatar }) {
  return <nav>{avatar}</nav>;
}

Короче говоря, не следует сразу обращаться к Context. Нужно посмотреть, нельзя ли лучше организовать свои компоненты без props’ов.

Может ли React Context заменить Redux?

Нельзя ответить однозначно.

Redux  —  более простой способ передачи данных для изучающих React. Потому что в Redux имеется React Context.

Однако, если состояние не обновлять, а просто передавать по дереву компонентов, то и не нужен Redux (универсальная библиотека управления состоянием).

Ограничения применимости React Context

Почему невозможно обновить значение, которое передается вниз с помощью React Сontext?

Комбинируя React Сontext с хуком useReducer можно создать импровизированную библиотеку управления состоянием без использования сторонней библиотеки. Но из соображений потери производительности практикуется это редко.

Проблема здесь в том, что React Context запускает повторный рендеринг.

Что произойдет, если передавать объект провайдеру React Context и обновлять в нем какое-либо свойство? В любом использующем этот контекст компоненте будет выполнен повторный рендеринг.

Подобные проблемы с производительностью не всегда возникают в небольших приложениях с несколькими не очень часто обновляемыми значениями состояния (например, данные темы). Но все меняется в приложении с множеством компонентов в иерархии, если часто обновлять состояния.

Читайте также:

Читайте нас в TelegramVK и Яндекс.Дзен


Перевод статьи Reed Barger: React Context: The Definitive Guide (2021)

Предыдущая статьяСравниваем целочисленное и линейное программирование в Python
Следующая статьяСпецификатор constexpr в C++: зачем он нужен и как работает