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

Где доступен контекст?

1.6 Junior🔥 171 комментариев
#JavaScript Core

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

Где доступен контекст (React Context)

React Context - это мощный механизм для передачи данных через дерево компонентов без необходимости передавать props на каждом уровне. Однако контекст доступен не везде. Давайте разберемся в правилах и ограничениях доступности контекста.

Базовое определение

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

import { createContext } from "react";

// Создание контекста
const AuthContext = createContext(null);

// Provider - обвертка, которая делает контекст доступным
export function AuthProvider({ children }) {
  const [user, setUser] = React.useState(null);
  
  return (
    <AuthContext.Provider value={{ user, setUser }}>
      {children}
    </AuthContext.Provider>
  );
}

Правило 1: Контекст доступен только внутри Provider

Контекст доступен только для компонентов, которые находятся внутри Provider.

function AppRoot() {
  return (
    <AuthProvider>
      {/* Контекст доступен здесь и внутри */}
      <Header />
      <Main />
      <Footer />
    </AuthProvider>
  );
}

function Sidebar() {
  // Если Sidebar вне AuthProvider, контекст не доступен!
  const auth = useContext(AuthContext); // Ошибка: null
  return null;
}

// Правильно: Sidebar внутри Provider
function AppRoot() {
  return (
    <AuthProvider>
      <Sidebar /> {/* Доступен контекст */}
    </AuthProvider>
  );
}

Правило 2: Контекст доступен только в компонентах

Контекст доступен только в компонентах React, не в обычных функциях JavaScript.

// ПЛОХО: контекст в обычной функции
const authContext = useContext(AuthContext); // Ошибка!

function getAuthStatus() {
  // Это не компонент React
  const auth = useContext(AuthContext); // ОШИБКА: хук вне компонента
  return auth;
}

// ХОРОШО: контекст в компоненте React
function AuthStatus() {
  const auth = useContext(AuthContext); // OK
  return <div>{auth ? "Авторизован" : "Не авторизован"}</div>;
}

// ХОРОШО: кастомный хук (это тоже компонент в смысле хуков)
function useAuth() {
  return useContext(AuthContext);
}

Правило 3: Контекст доступен в детях Provider

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

function App() {
  return (
    <AuthProvider>
      <Layout /> {/* Контекст доступен */}
    </AuthProvider>
  );
}

function Layout() {
  return (
    <div>
      <Header /> {/* Контекст доступен */}
      <Main /> {/* Контекст доступен */}
    </div>
  );
}

function Header() {
  return (
    <div>
      <UserMenu /> {/* Контекст доступен, даже глубоко вложен */}
    </div>
  );
}

function UserMenu() {
  const auth = useContext(AuthContext); // OK: контекст доступен
  return <div>{auth.user?.name}</div>;
}

Правило 4: Контекст не доступен выше Provider

Компоненты выше Provider не имеют доступа к контексту.

function OutsideProvider() {
  // ОШИБКА: это выше AuthProvider
  const auth = useContext(AuthContext); // null!
  return <div>Не работает</div>;
}

function App() {
  return (
    <div>
      <OutsideProvider /> {/* Контекст НЕ доступен */}
      
      <AuthProvider>
        <InsideProvider /> {/* Контекст ДОСТУПЕН */}
      </AuthProvider>
    </div>
  );
}

function InsideProvider() {
  const auth = useContext(AuthContext); // OK
  return <div>Работает</div>;
}

Правило 5: Контекст не доступен в параллельных ветвях

Если компонент находится в другой ветви дерева, контекст не доступен.

function App() {
  return (
    <AuthProvider>
      <Layout />
      {/* Контекст доступен */}
    </AuthProvider>
    
    <Sidebar />
    {/* Контекст НЕ доступен - вне Provider */}
  );
}

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

// root.tsx
import { AuthProvider } from "./contexts/AuthContext";
import { ThemeProvider } from "./contexts/ThemeContext";

export function Root() {
  return (
    <ThemeProvider>
      <AuthProvider>
        <App />
      </AuthProvider>
    </ThemeProvider>
  );
}

// Иерархия доступности:
// ThemeProvider
//   ├─ Theme контекст доступен
//   ├─ AuthProvider
//   │   ├─ Auth контекст доступен
//   │   ├─ Theme контекст доступен
//   │   └─ App
//   │       ├─ Auth контекст доступен
//   │       ├─ Theme контекст доступен
//   │       └─ ...

Вложенные контексты

Можно вложить несколько провайдеров. Внутренние контексты доступны везде внутри них.

function App() {
  return (
    <AuthProvider>
      <ThemeProvider>
        <LanguageProvider>
          <Layout />
          {/* Все три контекста доступны здесь */}
        </LanguageProvider>
      </ThemeProvider>
    </AuthProvider>
  );
}

function UserSettings() {
  const auth = useContext(AuthContext);        // OK
  const theme = useContext(ThemeContext);      // OK
  const language = useContext(LanguageContext); // OK
  
  return (
    <div>
      <p>Пользователь: {auth.user?.name}</p>
      <p>Тема: {theme.mode}</p>
      <p>Язык: {language.lang}</p>
    </div>
  );
}

Обычная ошибка: Использование контекста вне компонента

// НЕПРАВИЛЬНО
const UserContext = createContext(null);

// Использование ВНЕ компонента
const user = useContext(UserContext); // ОШИБКА!

function fetchUserData() {
  console.log(user); // undefined
}

// ПРАВИЛЬНО
function MyComponent() {
  const user = useContext(UserContext); // OK
  
  function fetchUserData() {
    console.log(user); // OK - у функции есть доступ
  }
  
  return <div>{user?.name}</div>;
}

Контекст и асинхронные операции

Контекст доступен в асинхронных функциях, если они вызваны из компонента.

function UserProfile() {
  const { user } = useContext(AuthContext);
  const [data, setData] = useState(null);
  
  useEffect(() => {
    // Асинхронная функция, вызванная из useEffect
    async function loadData() {
      const response = await fetch(`/api/user/${user.id}`);
      const userData = await response.json();
      setData(userData);
      // Контекст был доступен при создании функции
    }
    
    loadData();
  }, [user]); // user из контекста
  
  return <div>{data?.name}</div>;
}

// НЕПРАВИЛЬНО: контекст вне компонента
const { user } = useContext(AuthContext); // ОШИБКА!

async function loadUserData() {
  const response = await fetch(`/api/user/${user.id}`); // user undefined
}

Контекст в кастомных хуках

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

// Хук для доступа к контексту
function useAuth() {
  const context = useContext(AuthContext);
  
  if (!context) {
    throw new Error("useAuth должен быть внутри AuthProvider");
  }
  
  return context;
}

function UserCard() {
  const { user } = useAuth(); // OK - это хук, вызванный из компонента
  return <div>{user?.name}</div>;
}

// НЕПРАВИЛЬНО: вызов хука вне компонента
const auth = useAuth(); // ОШИБКА!

function getData() {
  console.log(auth); // undefined
}

Защита от неправильного использования

const UserContext = createContext(null);

// Кастомный хук с проверкой
export function useUserContext() {
  const context = useContext(UserContext);
  
  if (context === null) {
    throw new Error(
      "useUserContext должен быть внутри UserProvider. " +
      "Убедись, что компонент обернут в <UserProvider></UserProvider>"
    );
  }
  
  return context;
}

// Использование
function UserInfo() {
  const user = useUserContext(); // Выбросит понятную ошибку если не внутри Provider
  return <div>{user.name}</div>;
}

Визуальная схема доступности

<AuthProvider>
  ├─ <Header>
  │  └─ <UserMenu> контекст доступен
  │
  ├─ <Main>
  │  ├─ <Content> контекст доступен
  │  └─ <Sidebar> контекст доступен
  │
  └─ <Footer>
     └─ <Copyright> контекст доступен

<NotIncluded> контекст НЕ доступен

Почему контекст не доступен

const ThemeContext = createContext("light");

// Причина 1: компонент вне Provider
function Button() {
  const theme = useContext(ThemeContext); // null, потому что вне Provider
  return <button>Кнопка</button>;
}

function App() {
  return (
    <Button /> // Вне ThemeProvider - контекст не доступен
  );
}

// ИСПРАВЛЕНИЕ:
function App() {
  return (
    <ThemeProvider>
      <Button /> // Внутри ThemeProvider - контекст доступен
    </ThemeProvider>
  );
}

// Причина 2: использование вне компонента
const theme = useContext(ThemeContext); // ОШИБКА! Вне компонента

// ИСПРАВЛЕНИЕ: используй в компоненте
function ThemeButton() {
  const theme = useContext(ThemeContext); // OK
  return <button>Тема: {theme}</button>;
}

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

  1. Всегда оборачивай приложение в Provider

    export default function Root() {
      return (
        <AuthProvider>
          <ThemeProvider>
            <App />
          </ThemeProvider>
        </AuthProvider>
      );
    }
    
  2. Создавай кастомные хуки для контекстов

    export function useAuth() {
      const context = useContext(AuthContext);
      if (!context) throw new Error("useAuth вне AuthProvider");
      return context;
    }
    
  3. Проверяй, что компонент внутри Provider

    function MyComponent() {
      try {
        const auth = useAuth(); // Выбросит ошибку если вне Provider
        return <div>{auth.user?.name}</div>;
      } catch (error) {
        return <div>Ошибка: {error.message}</div>;
      }
    }
    

Заключение

Контекст доступен только внутри провайдера, для компонентов React, на любой глубине вложенности. Помни, что:

  1. Контекст не доступен выше Provider'а
  2. Контекст не доступен в обычных функциях (только в компонентах и хуках)
  3. Используй кастомные хуки для безопасного доступа
  4. Вложенные провайдеры делают внутренние контексты доступными для всех детей
  5. Контекст не доступен в параллельных ветвях дерева компонентов
Где доступен контекст? | PrepBro