Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Где доступен контекст (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>;
}
Лучшие практики
-
Всегда оборачивай приложение в Provider
export default function Root() { return ( <AuthProvider> <ThemeProvider> <App /> </ThemeProvider> </AuthProvider> ); } -
Создавай кастомные хуки для контекстов
export function useAuth() { const context = useContext(AuthContext); if (!context) throw new Error("useAuth вне AuthProvider"); return context; } -
Проверяй, что компонент внутри Provider
function MyComponent() { try { const auth = useAuth(); // Выбросит ошибку если вне Provider return <div>{auth.user?.name}</div>; } catch (error) { return <div>Ошибка: {error.message}</div>; } }
Заключение
Контекст доступен только внутри провайдера, для компонентов React, на любой глубине вложенности. Помни, что:
- Контекст не доступен выше Provider'а
- Контекст не доступен в обычных функциях (только в компонентах и хуках)
- Используй кастомные хуки для безопасного доступа
- Вложенные провайдеры делают внутренние контексты доступными для всех детей
- Контекст не доступен в параллельных ветвях дерева компонентов