Context API

В типичном React-приложении данные передаются сверху вниз (от родителя к дочернему компоненту) с помощью пропсов. Однако, этот способ может быть чересчур громоздким для некоторых типов пропсов (например, выбранный язык, UI-тема), которые необходимо передавать во многие компоненты в приложении.

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

Когда использовать Context ? 🤔

  • Контекст предназначен для обмена данными, которые можно считать «глобальными» для дерева компонентов React, таких как текущий аутентифицированный пользователь, тема или предпочтительный язык.
  • Через контекст можно прокинуть props минуя несколько дочерних уровней в нужный компонент.

API

React.createContext

const MyContext = React.createContext(defaultValue);

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

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

Обратите внимание

Eсли передать undefined как значение Provider, компоненты, использующие этот контекст, не будут использовать defaultValue.

Context.Provider

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

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

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

Обратите внимание

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

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

Нужно знать

Поскольку контекст использует ссылочную идентификацию, чтобы определить, когда нужно повторно рендерить, есть некоторые ошибки, которые могут вызвать непреднамеренное рендеринг в потребителях( Consumer ), когда Provider ререндерится. Например, приведенный ниже код будет повторно отображать всех потребителей( Consumer ) каждый раз, когда поставщик( Provider ) перерендерится, потому что всегда будет создаваться новый обьект:

🛑 Плохо

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

✅ Хорошо

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

⚠️Подробнее

Class.contextType

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

// create context
export const themes = {
light: {
foreground: "#000000",
background: "#eeeeee",
},
dark: {
foreground: "#ffffff",
background: "#222222",
},
};
const ThemeContext = React.createContext(
{ theme: themes.dark, toggleTheme: () => {} } // значение по умолчанию
);
// Themed button
class ThemedButton extends React.Component {
render() {
const { theme } = this.context;
return (
<button {...this.props} style={{ backgroundColor: theme.background }} />
);
}
}
ThemedButton.contextType = ThemeContext;
function ThemeTogglerButton() {
// ThemeTogglerButton получает из контекста
// не только значение UI-темы, но и функцию toggleTheme.
return (
<ThemeContext.Consumer>
{({ theme, toggleTheme }) => (
<button
onClick={toggleTheme}
style={{ backgroundColor: theme.background }}
>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
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}>
<ThemeTogglerButton />
</ThemeContext.Provider>
);
}
}

Практика 👩‍💻👨‍💻

Создать <UserProvider/> с дефолтным значение контекста

const UserContext = React.createContext({
user: {
firstName: "John",
lastName: "Doe",
age: 10,
},
updateUser: () => {}, // (newUser) => void
});

Создать следующую структуру компонентов:

function App() {
return (
<UserProvider>
<Header>
<User />
</Header>
<UserForm />
</UserProvider>
);
}
  • User - должен отобрадать данные текущего пользователя
  • UserForm - форма с полями:
    • firstName;
    • lastName;
    • age;

По клику на кнопку update данные юзера должны быть обновлены и отображены в компоненте User.

Почитать 📚

Edit this page on GitHub