React
May 3, 2022

Как использовать useContext() в React?

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

В этой статье вы узнаете, как использовать концепцию контекста в React.

Как использовать контекст?

Для использования контекста в React требуется 3 простых шага: создание контекста, предоставление контекста и использование контекста.

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

Встроенная функция createContext(default) создает экземпляр контекста:

import { createContext } from 'react';

const Context = createContext('Default Value');

Функция фабрика принимает один необязательный аргумент: значение по умолчанию.

2. Предоставление контекста

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

Чтобы задать значение контекста, используйте value prop, доступное в <Context.Provider value={value} />:

const Main = () => {
  const value = 'My Context Value';
  
  return (
    <Context.Provider value={value}>
      <MyComponent />
    </Context.Provider>
  );
}

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

Если вы хотите изменить значение контекста, просто обновите value.

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

Использование контекста может быть выполнено 2 способами.

Первый способ, который я рекомендую - это использовать React хук useContext(Context):

import { useContext } from 'react';

const MyComponent = () => {
  const value = useContext(Context);
  return <span>{value}</span>;
}

Хук возвращает значение контекста:value = useContext(Context). Хук также гарантирует повторную перерисовку компонента при изменении значения контекста.

Второй способ заключается в использовании функции рендеринга, предоставляемой в качестве дочернего элемента Context.Consumer - специальный компонент, доступный в экземпляре контекста:

const MyComponent = () => (
    <Context.Consumer>
      {value => <span>{value}</span>}
    </Context.Consumer>
);

Опять же, в случае изменения значения контекста, <Context.Consumer> повторно вызовет свою функцию рендеринга.

У вас может быть столько потребителей, сколько вы хотите для одного контекста. Если значение контекста изменяется (путем изменения value prop поставщика <Context.Provider value={value} />), то все потребители немедленно уведомляются и повторно перерисовываются.

Если потребитель не обернут внутри поставщика, но все еще пытается получить доступ к значению контекста (используя useContext(Context) или <Context.Consumer>), тогда значение контекста будет аргументом значения по умолчанию, предоставленным заводской функции createContext(defaultValue), которая создала контекст.

Когда нужен контекст?

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

Вы можете хранить внутри контекста:

  • глобальное состояние
  • тему
  • конфигурацию приложения
  • аутентифицированное имя
  • пользователя настройки
  • пользователя предпочитаемый язык
  • набор служб

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

Во-первых, интеграция контекста добавляет сложности. Создание контекста, перенос данных в поставщика, использование useContext() в каждом потребителе — это увеличивает сложность.

Во-вторых, добавление контекста затрудняет модульное тестирование компонентов. Во время модульного тестирования вам придется обернуть потребительские компоненты в поставщика контекста. Включая компоненты, на которые косвенно влияет контекст — предки потребителей контекста!

Пример использования: глобальное имя пользователя

Самый простой способ передачи данных от родительского к дочернему компоненту - это когда родительский компонент присваивает реквизиты своему дочернему компоненту:

const Application = () => {
  const userName = "John Smith";
  return <UserInfo userName={userName} />;
}

const UserInfo = ({ userName }) => <span>{userName}</span>;

Родительский компонент <Application /> присваивает данные имени пользователя своему дочернему компоненту <UserInfo name={userName} />, используя проп userName.

Это обычный способ передачи данных с использованием пропсов. Вы можете использовать этот подход без проблем.

Ситуация меняется, когда дочерний компонент <UserInfo /> не является прямым дочерним компонентом <Application />, а содержится в нескольких предках.

Например, предположим, что компонент <Application /> (тот, у которого есть имя пользователя глобальных данных) отображает компонент <Layout />, который, в свою очередь, отображает компонент <Header />, который, в свою очередь, в конечном итоге отображает компонент <UserInfo /> (который хотел бы получить доступ к имени пользователя).

Вот как будет выглядеть такая структура:

const Application = () => {
  const userName = "John Smith";
  return (
    <Layout userName={userName}>
      Main content
    </Layout>
  );
};

const Layout = ({ children, userName }) => (
    <div>
      <Header userName={userName} />
      <main>
        {children}
      </main>
    </div>
);

const Header = ({ userName }) => (
    <header>
      <UserInfo userName={userName} />
    </header>
);

const UserInfo = ({ userName }) => <span>{userName}</span>;

Вы можете видеть проблему: потому что компонент <UserInfo /> отображается глубоко в дереве, и все родительские компоненты (<Layout /> и <Header />) должны передавать проп имени пользователя.

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

Контекст - это возможное решение. Давайте посмотрим, как его применить.

Контекст для спасения

В качестве краткого напоминания, для применения контекста React требуются 3 участника: контекст, поставщик и потребитель.

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

import { useContext, createContext } from 'react';

const UserContext = createContext('Unknown');

const Application = () => {
  const userName = "John Smith";
  
  return (
    <UserContext.Provider value={userName}>
      <Layout>
        Main content
      </Layout>
    </UserContext.Provider>
  );
}

const Layout = ({ children }) => (
    <div>
      <Header />
      <main>
        {children}
      </main>
    </div>
);

const Header = () => (
    <header>
      <UserInfo />
    </header>
);

const UserInfo = () => {
  const userName = useContext(UserContext);
  
  return <span>{userName}</span>;
}

Давайте более подробно рассмотрим, что было сделано.

Во-первых, const UserContext = createContext('Unknown') создает контекст, который будет содержать информацию об имени пользователя.

Во-вторых, внутри компонента <Application /> дочерние компоненты приложения заключены в поставщика контекста: <UserContext.Provider value={userName}>. Обратите внимание, что значение prop компонента provider имеет важное значение: именно так вы устанавливаете значение контекста.

И в итоге <UserInfo /> становится потребителем контекста с помощью встроенного хука useContext(UserContext). Хук вызывается с контекстом в качестве аргумента и возвращает значение имени пользователя.

Промежуточные компоненты <Layout /> и <Header /> не должны передавать пропс с именем пользователя. Это большое преимущество контекста: он снимает бремя передачи данных через промежуточные компоненты.

Когда контекст меняется

Когда значение контекста изменяется путем изменения значения prop поставщика контекста (<Context.Provider value={value} />), то все его потребители получают уведомление и повторно перерисовываются.

Например, если я изменю имя пользователя с 'John Smith' на 'Smith, John Smith', то <UserInfo /> немедленно повторно перерисуется с последним значеним контекста.

Итоги

Контекст в React - это концепция, которая позволяет вам предоставлять дочерним компонентам глобальные данные, независимо от того, насколько глубоко они находятся в дереве компонентов.

Использование контекста требует 3 шагов: создание, предоставление и использование контекста.

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

Не забывайте подписываться на телеграм канал, чтобы не пропустить новые статьи, до встречи ✌️

Читайте еще: