Что такое Context API в React?
Context API в React
Context API — встроенный инструмент React, предназначенный для передачи данных, которые нужны многим компонентам на разных уровнях иерархии, без необходимости пробрасывать пропсы через промежуточные компоненты (проблема «prop drilling»).
Когда использовать Context
Context подходит для данных, которые можно считать «глобальными» для дерева компонентов: текущий пользователь, тема оформления, язык интерфейса, настройки. Для сложного серверного стейта или частых локальных обновлений лучше подойдут специализированные решения (Zustand, Redux, React Query).
Как устроен Context API
Context состоит из трёх частей:
React.createContext(defaultValue)— создаёт объект контекста с дефолтным значением. Дефолт используется только если компонент находится вне дерева провайдера.Context.Provider— компонент-обёртка, передающий значение всем потребителям ниже по дереву.useContext(Context)— хук, подписывающий компонент на изменения контекста.
// Создание контекста с типом
const ThemeContext = React.createContext<'light' | 'dark'>('light');
Пример использования
// Провайдер оборачивает дерево
function App() {
const [theme, setTheme] = React.useState<'light' | 'dark'>('light');
return (
<ThemeContext.Provider value={theme}>
<Header />
<Main />
</ThemeContext.Provider>
);
}
// Потребитель читает значение через хук
function Header() {
const theme = useContext(ThemeContext);
return <header className={theme}>...</header>;
}
Оптимизация и ре-рендеры
Каждое изменение value в провайдере вызывает ре-рендер всех подписанных через useContext компонентов, даже если они используют только часть данных. Основные способы оптимизации:
- Разделение контекстов — хранить несвязанные данные в разных контекстах.
- Мемоизация
valueчерезuseMemo, чтобы не создавать новый объект на каждый рендер родителя. - Паттерн «разделение стейта и диспатча» — отдельный контекст для данных и отдельный для функций обновления.
const CountContext = React.createContext<number>(0);
const CountDispatchContext = React.createContext<React.Dispatch<React.SetStateAction<number>>>(() => {});
function CounterProvider({ children }: { children: React.ReactNode }) {
const [count, setCount] = React.useState(0);
// setCount стабилен — подписчики диспатча не ре-рендерятся при изменении count
return (
<CountDispatchContext.Provider value={setCount}>
<CountContext.Provider value={count}>
{children}
</CountContext.Provider>
</CountDispatchContext.Provider>
);
}
Context vs внешний стейт-менеджер
Context хорош для редко меняющихся глобальных данных. Если данные обновляются часто или логика сложная — стоит рассмотреть Zustand или Redux Toolkit, которые предоставляют селекторы и позволяют подписываться только на нужный срез стейта.
Что хочет услышать интервьюер
Понимание проблемы prop drilling и того, как Context её решает
Знание трёх составляющих: createContext, Provider, useContext
Осознание, что Context вызывает ре-рендер всех подписчиков при изменении value
Умение оптимизировать контекст: разделение контекстов, useMemo для value
Понимание границ применимости Context vs внешних стейт-менеджеров
Пример: Базовый Context с провайдером и хуком-обёрткой
import React, { createContext, useContext, useState } from 'react';
interface AuthContextValue {
user: string | null;
login: (name: string) => void;
logout: () => void;
}
// Дефолт — null, будет использоваться только вне провайдера
const AuthContext = createContext<AuthContextValue | null>(null);
// Хук-обёртка с проверкой наличия провайдера
export function useAuth(): AuthContextValue {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error('useAuth должен использоваться внутри AuthProvider');
return ctx;
}
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<string | null>(null);
const login = (name: string) => setUser(name);
const logout = () => setUser(null);
// useMemo предотвращает создание нового объекта value на каждый рендер
const value = React.useMemo(
() => ({ user, login, logout }),
[user]
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
// Использование в любом компоненте внутри AuthProvider
function UserGreeting() {
const { user, logout } = useAuth();
if (!user) return <span>Гость</span>;
return (
<div>
<span>Привет, {user}!</span>
<button onClick={logout}>Выйти</button>
</div>
);
}
Типичные ошибки
Хранение всего приложения в одном контексте, что приводит к лишним ре-рендерам
Передача нового объекта в value без useMemo — провайдер пересоздаёт объект на каждый рендер родителя
Использование Context для часто меняющихся данных (например, позиция курсора) вместо более подходящих инструментов
Путаница между дефолтным значением createContext и значением провайдера — дефолт нужен только при отсутствии провайдера в дереве
Игнорирование разделения контекста на «стейт» и «диспатч», что заставляет ненужные компоненты перерисовываться


