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

В React 16.3 была представлена новая версия Context API, которая упростила передачу данных через дерево компонентов и добавила новые возможности для управления контекстом.

В новой версии Context API введены два новых компонента - Provider и Consumer. Компонент Provider позволяет определить контекст и передать его значения дочерним компонентам. Компонент Consumer позволяет получить значения контекста из родительского компонента.

Также были добавлены новые методы для обновления контекста - createContext() и useContext(). Метод createContext() позволяет создать контекст с начальными значениями, а метод useContext() позволяет получить значения контекста в функциональных компонентах.

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

import React, { createContext, useState, useContext } from "react"
 
// Создание контекста
const ThemeContext = createContext("light")
 
function App() {
  const [theme, setTheme] = useState("light")
 
  // Обработчик изменения темы
  const handleThemeChange = () => {
    setTheme(theme === "light" ? "dark" : "light")
  }
 
  // Передача значения контекста через Provider
  return (
    <ThemeContext.Provider value={theme}>
      <Toolbar onThemeChange={handleThemeChange} />
    </ThemeContext.Provider>
  )
}
 
function Toolbar(props) {
  return (
    <div>
      <ThemedButton onClick={props.onThemeChange} />
    </div>
  )
}
 
function ThemedButton(props) {
  // Получение значения контекста через useContext
  const theme = useContext(ThemeContext)
  return (
    <button
      {...props}
      style={{
        backgroundColor: theme === "light" ? "#fff" : "#000",
        color: theme === "light" ? "#000" : "#fff",
      }}
    />
  )
}
 
export default App

В данном примере мы создаем контекст ThemeContext и определяем начальное значение light. Мы передаем значение контекста через компонент Provider и обрабатываем изменение темы в родительском компоненте App. В дочерних компонентах Toolbar и ThemedButton мы получаем значение контекста через useContext() и используем его для определения стилей элементов. При нажатии на кнопку в компоненте ThemedButton вызывается функция handleThemeChange(), которая обновляет значение состояния и передает его через контекст.

React.Context

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

Для того, чтобы создать контекст, используем :

const { Provider , Consumer } = React.createService()
 
<Provider value={someValue}>
	.. // провайдер оборачивает часть приложения
 
// Компонент `Provider` принимает проп `value`, который будет передан во все компоненты, использующие этот контекст и являющиеся потомками этого компонента `Provider`
 
// *Предостережения*
</Provider>
<Consumer>
  {" "}
  {
    (someValue) => <MyComponent data={someValue} />
 
    // Эта функция принимает текущее значение контекста и возвращает React-компонент. Передаваемый аргумент `value` будет равно значению компонента `Provider`.
    // Если такого компонента `Provider` не существует, аргумент `value` будет равен значению `defaultValue`, которое было передано в `createContext()`.
  }{" "}
</Consumer>

Подробнее про паттерн «функция как дочерний компонент» можно узнать на странице Рендер-пропсы.

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

Для того, чтобы создать контекст, используем:

const MyContext = React.createContext(defaultValue)

Создаёт объект Context. Когда React рендерит компонент, который подписан на этот объект, React получит текущее значение контекста из ближайшего подходящего Provider выше в дереве компонентов.

%%Аргумент defaultValue используется только в том случае, если для компонента нет подходящего Provider выше в дереве. Значение по умолчанию может быть полезно для тестирования компонентов в изоляции без необходимости оборачивать их. Обратите внимание: если передать undefined как значение Provider, компоненты, использующие этот контекст, не будут использовать defaultValue.%%

Context.Provider

<MyContext.Provider value={/* некоторое значение */}>

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

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

Все потребители, которые являются потомками Provider, будут повторно рендериться, как только проп value у Provider изменится. Потребитель (включая .contextType и useContext) перерендерится при изменении контекста, даже если его родитель, не использующий данный контекст, блокирует повторные рендеры с помощью shouldComponentUpdate.

Изменения определяются с помощью сравнения нового и старого значения, используя алгоритм, аналогичный Object.is.

Примечание Способ, по которому определяются изменения, может вызвать проблемы при передаче объекта в value: смотрите Предостережения.

Class.contextType

class MyClass extends React.Component {
  componentDidMount() {
    let value = this.context
    /* выполнить побочный эффект на этапе монтирования, используя значение MyContext */
  }
  componentDidUpdate() {
    let value = this.context
    /* ... */
  }
  componentWillUnmount() {
    let value = this.context
    /* ... */
  }
  render() {
    let value = this.context
    /* отрендерить что-то, используя значение MyContext */
  }
}
MyClass.contextType = MyContext

В свойство класса contextType может быть назначен объект контекста, созданный с помощью React.createContext(). С помощью этого свойства вы можете использовать ближайшее и актуальное значение указанного контекста при помощи this.context. В этом случае вы получаете доступ к контексту, как во всех методах жизненного цикла, так и в рендер-методе.

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

Если вы используете экспериментальный синтаксис публичных полей класса, вы можете использовать static поле класса, чтобы инициализировать ваш contextType.

class MyClass extends React.Component {
  static contextType = MyContext
  render() {
    let value = this.context
    /* отрендерить что-то, используя значение MyContext */
  }
}

Context.Consumer

<MyContext.Consumer>
  {value => /* отрендерить что-то, используя значение контекста */}
</MyContext.Consumer>

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

Consumer принимает функцию в качестве дочернего компонента. Эта функция принимает текущее значение контекста и возвращает React-компонент. Передаваемый аргумент value будет равен ближайшему (вверх по дереву) значению этого контекста, а именно пропу value компонента Provider. Если такого компонента Provider не существует, аргумент value будет равен значению defaultValue, которое было передано в createContext().

Примечание Подробнее про паттерн «функция как дочерний компонент» можно узнать на странице Рендер-пропсы.

Context.displayName

Объекту Context можно задать строковое свойство displayName. React DevTools использует это свойство при отображении контекста.

К примеру, следующий компонент будет отображаться под именем MyDisplayName в DevTools:

const MyContext = React.createContext(/* некоторое значение */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" в DevTools
<MyContext.Consumer> // "MyDisplayName.Consumer" в DevTools

Динамический контекст

Более сложный пример динамических значений для UI-темы:

export const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee',
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222',
  },
};
 
export const ThemeContext = React.createContext(  themes.dark // значение по умолчанию);
import { ThemeContext } from "./theme-context"
 
class ThemedButton extends React.Component {
  render() {
    let props = this.props
    let theme = this.context
    return <button {...props} style={{ backgroundColor: theme.background }} />
  }
}
ThemedButton.contextType = ThemeContext
export default ThemedButton
import {ThemeContext, themes} from './theme-context';
import ThemedButton from './themed-button';
 
// Промежуточный компонент, который использует ThemedButton
function Toolbar(props) {
  return (
    <ThemedButton onClick={props.changeTheme}>
      Change Theme
    </ThemedButton>
  );
}
 
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: themes.light,
    };
 
    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };
  }
 
  render() {
    // ThemedButton внутри ThemeProvider использует    // значение светлой UI-темы из состояния, в то время как    // ThemedButton, который находится вне ThemeProvider,    // использует тёмную UI-тему из значения по умолчанию    return (
      <Page>
        <ThemeContext.Provider value={this.state.theme}>          <Toolbar changeTheme={this.toggleTheme} />        </ThemeContext.Provider>        <Section>
          <ThemedButton />        </Section>
      </Page>
    );
  }
}
 
const root = ReactDOM.createRoot(
  document.getElementById('root')
);
root.render(<App />);

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

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

// Убедитесь, что форма значения по умолчанию,
// передаваемого в createContext, совпадает с формой объекта,
// которую ожидают потребители контекста.
export const ThemeContext = React.createContext({
  theme: themes.dark,
  toggleTheme: () => {},
})
import {ThemeContext} from './theme-context';
 
function ThemeTogglerButton() {
  // ThemeTogglerButton получает из контекста  // не только значение UI-темы, но и функцию toggleTheme.  return (
    <ThemeContext.Consumer>
      {({theme, toggleTheme}) => (        <button
          onClick={toggleTheme}
          style={{backgroundColor: theme.background}}>
          Toggle Theme
        </button>
      )}
    </ThemeContext.Consumer>
  );
}
 
export default ThemeTogglerButton;
import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';
 
class App extends React.Component {
  constructor(props) {
    super(props);
 
    this.toggleTheme = () => {
      this.setState(state => ({
        theme:
          state.theme === themes.dark
            ? themes.light
            : themes.dark,
      }));
    };
 
    // Состояние хранит функцию для обновления контекста,    // которая будет также передана в Provider-компонент.    this.state = {
      theme: themes.light,
      toggleTheme: this.toggleTheme,    };
  }
 
  render() {
    // Всё состояние передаётся в качестве значения контекста    return (
      <ThemeContext.Provider value={this.state}>        <Content />
      </ThemeContext.Provider>
    );
  }
}
 
function Content() {
  return (
    <div>
      <ThemeTogglerButton />
    </div>
  );
}
 
const root = ReactDOM.createRoot(
  document.getElementById('root')
);
root.render(<App />);

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

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

// Контекст UI-темы, со светлым значением по умолчанию
const ThemeContext = React.createContext("light")
 
// Контекст активного пользователя
const UserContext = React.createContext({
  name: "Guest",
})
 
class App extends React.Component {
  render() {
    const { signedInUser, theme } = this.props
 
    // Компонент App, который предоставляет начальные значения контекстов
    return (
      <ThemeContext.Provider value={theme}>
        {" "}
        <UserContext.Provider value={signedInUser}>
          {" "}
          <Layout />
        </UserContext.Provider>{" "}
      </ThemeContext.Provider>
    )
  }
}
 
function Layout() {
  return (
    <div>
      <Sidebar />
      <Content />
    </div>
  )
}
 
// Компонент, который может использовать несколько контекстов
function Content() {
  return (
    <ThemeContext.Consumer>
      {" "}
      {(theme) => (
        <UserContext.Consumer>
          {" "}
          {(user) => <ProfilePage user={user} theme={theme} />}{" "}
        </UserContext.Consumer>
      )}{" "}
    </ThemeContext.Consumer>
  )
}

Если два или более значений контекста часто используются вместе, возможно, вам стоит рассмотреть создание отдельного компонента, который будет передавать оба значения дочерним компонентам с помощью паттерна «рендер-пропс».

Предостережения

Контекст использует сравнение по ссылкам, чтобы определить, когда запускать последующий рендер. Из-за этого существуют некоторые подводные камни, например, случайные повторные рендеры потребителей, при перерендере родителя Provider-компонента. В следующем примере будет происходить повторный рендер потребителя каждый повторный рендер Provider-компонента, потому что новый объект, передаваемый в value, будет создаваться каждый раз:

class App extends React.Component {
  render() {
    return (
      <MyContext.Provider value={{ something: "something" }}>
        {" "}
        <Toolbar />
      </MyContext.Provider>
    )
  }
}

Один из вариантов решения этой проблемы — хранение этого объекта в состоянии родительского компонента:

class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      value: { something: "something" },
    }
  }
 
  render() {
    return (
      <MyContext.Provider value={this.state.value}>
        {" "}
        <Toolbar />
      </MyContext.Provider>
    )
  }
}

Отличия между useContext() и Redux

useContext()Redux
useContext() - это хукRedux - это библиотека управления состоянием.
Он используется для обмена данными.Он используется для управления данными и состоянием.
Изменения вносятся с учетом значения контекста.Изменения вносятся с помощью чистых функций, т.е. редукторов.
Мы можем изменить состояние в нем.Состояние доступно только для чтения. Мы не можем изменить их напрямую.
Он повторно рендерит все компоненты всякий раз, когда происходит какое-либо обновление в prop значения поставщика.Производит только повторный рендеринг обновленных компонентов.
Его лучше использовать с небольшими приложениями.Идеально подходит для более крупных применений.
Хук легче для понимания и требует меньше кода.Redux довольно сложен для понимания.

as React19

В React 19 вы можете использовать <Context> в качестве провайдера вместо <Context.Provider>:

const ThemeContext = createContext('');
 
function App({children}) {
  return (
    <ThemeContext value="dark">
      {children}
    </ThemeContext>
  );  
}

Новые провайдеры контекста могут использовать <Context>, и мы опубликуем codemod для преобразования существующих провайдеров. В будущих версиях мы планируем объявить <Context.Provider> устаревшим.


Назад