← Назад к вопросам

Для чего использовал useContext?

2.0 Middle🔥 251 комментариев
#React#State Management

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Для чего использовать useContext

useContext — это React хук, который позволяет избежать передачи props сквозь все уровни компонентов (prop drilling). Он предоставляет способ делиться данными между компонентами без необходимости передавать их через props каждого промежуточного компонента. useContext идеально подходит для глобального состояния, тем, языков и аутентификации.

Основная проблема: Prop Drilling

Без useContext нужно передавать props через все компоненты:

// Компонент A передает theme в B
function App() {
  const theme = 'dark';
  return <ComponentB theme={theme} />;
}

// Компонент B передает theme в C (не использует)
function ComponentB({ theme }) {
  return <ComponentC theme={theme} />;
}

// Компонент C передает theme в D (не использует)
function ComponentC({ theme }) {
  return <ComponentD theme={theme} />;
}

// Только D использует theme
function ComponentD({ theme }) {
  return <div className={`theme-${theme}`}>Content</div>;
}

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

Решение: Context API + useContext

Шаг 1: Создание Context

// ThemeContext.js
import { createContext } from 'react';

const ThemeContext = createContext();

export default ThemeContext;

Шаг 2: Предоставление значения через Provider

import ThemeContext from './ThemeContext';

function App() {
  const [theme, setTheme] = React.useState('light');

  return (
    // Provider оборачивает компоненты, которым нужен context
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Header />
      <Main />
      <Footer />
    </ThemeContext.Provider>
  );
}

Шаг 3: Использование useContext в дочерних компонентах

import { useContext } from 'react';
import ThemeContext from './ThemeContext';

function DeepComponent() {
  // Получаем значение из контекста напрямую
  const { theme, setTheme } = useContext(ThemeContext);

  return (
    <div className={`bg-${theme}`}>
      <p>Current theme: {theme}</p>
      <button onClick={() => setTheme('dark')}>
        Switch to Dark
      </button>
    </div>
  );
}

Практический пример: Тема приложения

// ThemeContext.js
import { createContext, useState } from 'react';

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');
  const [primaryColor, setPrimaryColor] = useState('#0066ff');

  const toggleTheme = () => {
    setTheme(prev => prev === 'light' ? 'dark' : 'light');
  };

  const value = {
    theme,
    toggleTheme,
    primaryColor,
    setPrimaryColor
  };

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  );
}

export default ThemeContext;

// App.js
import { ThemeProvider } from './ThemeContext';

function App() {
  return (
    <ThemeProvider>
      <Header />
      <Main />
      <Footer />
    </ThemeProvider>
  );
}

// Header.js
import { useContext } from 'react';
import ThemeContext from './ThemeContext';

function Header() {
  const { theme, toggleTheme, primaryColor } = useContext(ThemeContext);

  return (
    <header className={`bg-${theme}`}>
      <h1>My App</h1>
      <button
        onClick={toggleTheme}
        style={{ backgroundColor: primaryColor }}
      >
        Toggle Theme
      </button>
    </header>
  );
}

Пример: Аутентификация пользователя

// AuthContext.js
import { createContext, useState, useEffect } from 'react';

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Проверяем, авторизован ли пользователь
    const checkAuth = async () => {
      try {
        const response = await fetch('/api/me');
        if (response.ok) {
          const userData = await response.json();
          setUser(userData);
        }
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };

    checkAuth();
  }, []);

  const login = async (email, password) => {
    setLoading(true);
    try {
      const response = await fetch('/api/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password })
      });
      const userData = await response.json();
      setUser(userData);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  const logout = () => {
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, loading, error, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export default AuthContext;

// ProtectedRoute.js
import { useContext } from 'react';
import AuthContext from './AuthContext';

function ProtectedRoute({ children }) {
  const { user, loading } = useContext(AuthContext);

  if (loading) return <div>Loading...</div>;
  if (!user) return <div>Access Denied</div>;

  return children;
}

Пример: Множественные контексты

// Комбинируем несколько контекстов
function App() {
  return (
    <AuthProvider>
      <ThemeProvider>
        <NotificationProvider>
          <Header />
          <Main />
          <Footer />
        </NotificationProvider>
      </ThemeProvider>
    </AuthProvider>
  );
}

// Использование нескольких контекстов в компоненте
function Component() {
  const { user } = useContext(AuthContext);
  const { theme } = useContext(ThemeContext);
  const { notifications } = useContext(NotificationContext);

  return (
    <div className={`theme-${theme}`}>
      <p>User: {user?.name}</p>
      <p>Notifications: {notifications.length}</p>
    </div>
  );
}

Оптимизация: Избегаем ненужных перерендеров

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

// Разделяем значение и функции обновления
const ThemeValueContext = createContext();
const ThemeActionContext = createContext();

export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState('light');

  const value = { theme };
  const actions = { setTheme };

  return (
    <ThemeValueContext.Provider value={value}>
      <ThemeActionContext.Provider value={actions}>
        {children}
      </ThemeActionContext.Provider>
    </ThemeValueContext.Provider>
  );
}

// Или используем useMemo для оптимизации
const value = useMemo(() => ({ theme, setTheme }), [theme]);

return (
  <ThemeContext.Provider value={value}>
    {children}
  </ThemeContext.Provider>
);

Лучшие практики

Создание кастомного хука

// Вместо useContext везде
export function useTheme() {
  const context = useContext(ThemeContext);
  if (!context) {
    throw new Error('useTheme must be used within ThemeProvider');
  }
  return context;
}

// Теперь можно просто
function Component() {
  const { theme, toggleTheme } = useTheme();
  // ...
}

Когда использовать useContext

Идеально подходит для:

  • Глобальная тема приложения
  • Аутентификация и данные пользователя
  • Предпочтения языка и локализация
  • Глобальные уведомления
  • Доступные для всех компоненты конфигурации

Не подходит для:

  • Часто изменяющихся данных (высокая частота обновлений)
  • Очень сложного состояния (лучше использовать Redux)
  • Данных, специфичных для одного компонента (использовать state)

Альтернативы

// Для более сложного состояния используй useReducer
const [state, dispatch] = useReducer(reducer, initialState);

// Для управления состоянием на уровне приложения используй Redux, Zustand или MobX

Итоги

  • useContext избегает prop drilling
  • Идеален для глобального состояния (тема, аутентификация)
  • Нужен Context.Provider на уровне приложения
  • Создавай кастомные хуки для лучшей типизации
  • Оптимизируй с помощью useMemo если часто обновляется
  • Разделяй контексты по функциям (Auth, Theme, Notifications)
Для чего использовал useContext? | PrepBro