Для чего используется Context API?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Context API в React: назначение и применение
Context API — это встроенный в React механизм для передачи данных через дерево компонентов без необходимости передавать props на каждом уровне. Это решает проблему "prop drilling" (передача props через множество промежуточных компонентов).
Проблема: Prop Drilling
Представь ситуацию, когда нужно передать данные с верхнего уровня на нижний:
// ❌ Плохо: свойства передаются через все промежуточные компоненты
function App() {
const user = { name: "John", role: "admin" };
return <Layout user={user} />;
}
function Layout({ user }) {
return <Sidebar user={user} />;
}
function Sidebar({ user }) {
return <Menu user={user} />;
}
function Menu({ user }) {
return <UserInfo user={user} />;
}
function UserInfo({ user }) {
return <div>{user.name} ({user.role})</div>;
}
В этом примере user передаётся через 4 компонента, которые не используют эти данные напрямую.
Решение: Context API
// ✅ Хорошо: данные доступны через Context
import { createContext, useContext } from "react";
// 1. Создаём Context
const UserContext = createContext();
// 2. Создаём Provider для предоставления данных
export function UserProvider({ children }) {
const user = { name: "John", role: "admin" };
return (
<UserContext.Provider value={user}>
{children}
</UserContext.Provider>
);
}
// 3. Создаём хук для удобного доступа
export function useUser() {
return useContext(UserContext);
}
// 4. Используем
function App() {
return (
<UserProvider>
<Layout />
</UserProvider>
);
}
function Layout() {
return <Sidebar />;
}
function Sidebar() {
return <Menu />;
}
function Menu() {
return <UserInfo />;
}
function UserInfo() {
const user = useUser(); // Получаем данные напрямую!
return <div>{user.name} ({user.role})</div>;
}
Основные применения Context API
1. Тема приложения (Light/Dark Mode)
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState(() => {
// Загружаем из localStorage
return localStorage.getItem("theme") || "light";
});
const toggleTheme = () => {
const newTheme = theme === "light" ? "dark" : "light";
setTheme(newTheme);
localStorage.setItem("theme", newTheme);
};
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
return useContext(ThemeContext);
}
// Использование в компонентах
function Header() {
const { theme, toggleTheme } = useTheme();
return (
<header className={theme === "dark" ? "bg-gray-900" : "bg-white"}>
<button onClick={toggleTheme}>
Switch to {theme === "light" ? "dark" : "light"} mode
</button>
</header>
);
}
2. Авторизация и пользовательские данные
const AuthContext = createContext();
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// Проверяем авторизацию при загрузке приложения
checkAuth().then(userData => {
setUser(userData);
setIsLoading(false);
}).catch(() => setIsLoading(false));
}, []);
const login = async (email, password) => {
const userData = await api.login(email, password);
setUser(userData);
localStorage.setItem("token", userData.token);
};
const logout = () => {
setUser(null);
localStorage.removeItem("token");
};
return (
<AuthContext.Provider value={{ user, isLoading, login, logout }}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
return useContext(AuthContext);
}
// Защищённый маршрут
function ProtectedRoute({ children }) {
const { user, isLoading } = useAuth();
if (isLoading) return <Spinner />;
if (!user) return <Navigate to="/login" />;
return children;
}
3. Языковые настройки (i18n)
const LanguageContext = createContext();
const translations = {
en: {
hello: "Hello",
goodbye: "Goodbye",
},
ru: {
hello: "Привет",
goodbye: "До свидания",
},
};
export function LanguageProvider({ children }) {
const [language, setLanguage] = useState("en");
const changeLanguage = (lang) => {
setLanguage(lang);
localStorage.setItem("language", lang);
};
const t = (key) => translations[language][key];
return (
<LanguageContext.Provider value={{ language, changeLanguage, t }}>
{children}
</LanguageContext.Provider>
);
}
export function useLanguage() {
return useContext(LanguageContext);
}
function Greeting() {
const { t } = useLanguage();
return <h1>{t("hello")}</h1>;
}
4. Модальные окна и уведомления
const NotificationContext = createContext();
export function NotificationProvider({ children }) {
const [notifications, setNotifications] = useState([]);
const addNotification = (message, type = "info") => {
const id = Date.now();
setNotifications(prev => [...prev, { id, message, type }]);
// Автоматически удаляем через 3 секунды
setTimeout(() => {
removeNotification(id);
}, 3000);
};
const removeNotification = (id) => {
setNotifications(prev => prev.filter(n => n.id !== id));
};
return (
<NotificationContext.Provider value={{ addNotification, removeNotification }}>
{children}
<NotificationContainer notifications={notifications} />
</NotificationContext.Provider>
);
}
export function useNotification() {
const context = useContext(NotificationContext);
return {
success: (msg) => context.addNotification(msg, "success"),
error: (msg) => context.addNotification(msg, "error"),
info: (msg) => context.addNotification(msg, "info"),
};
}
// Использование
function Form() {
const { success, error } = useNotification();
const handleSubmit = async (data) => {
try {
await api.save(data);
success("Data saved successfully!");
} catch (err) {
error("Failed to save data");
}
};
return <form onSubmit={handleSubmit}>{/* ... */}</form>;
}
5. Глобальные настройки приложения
const AppConfigContext = createContext();
export function AppConfigProvider({ children }) {
const [config, setConfig] = useState({
apiBaseUrl: process.env.REACT_APP_API_URL,
enableAnalytics: true,
pageSize: 20,
maxRetries: 3,
});
const updateConfig = (newConfig) => {
setConfig(prev => ({ ...prev, ...newConfig }));
};
return (
<AppConfigContext.Provider value={{ config, updateConfig }}>
{children}
</AppConfigContext.Provider>
);
}
export function useAppConfig() {
return useContext(AppConfigContext);
}
Когда использовать Context API?
Context API хорошо подходит для:
- Частые изменения: авторизация, тема, язык
- Глубокие деревья компонентов: когда данные нужны на разных уровнях
- Небольшие проекты: для быстрого прототипирования
- Данные, которые редко меняются: конфигурация приложения
Когда НЕ использовать?
Не используй Context API, если:
- Данные часто меняются (например, реальтайм данные) — используй Redux/Zustand
- Нужен сложный state management — используй Redux Toolkit
- Нужна временная шкала (devtools) — используй Redux
- Нужна нормализация данных — используй Redux
// ❌ Плохо: Context для часто меняющихся данных
const HeavyUpdatesContext = createContext();
function Provider({ children }) {
const [data, setData] = useState({});
setInterval(() => {
setData(prev => ({ ...prev, timestamp: Date.now() })); // Обновляется каждую секунду
}, 1000);
return (
<HeavyUpdatesContext.Provider value={data}>
{children}
</HeavyUpdatesContext.Provider>
);
}
// Все компоненты будут перерендеривать на каждое обновление!
Оптимизация Context
Чтобы избежать ненужных перерендеров, разделяй Context на части:
// ✅ Хорошо: отделяем статичные значения от часто меняющихся
const AuthStateContext = createContext(); // Реже меняется
const AuthActionsContext = createContext(); // Функции (стабильны)
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const value = { user }; // Объект для состояния
const actions = {
login: async (email, password) => {/* ... */},
logout: () => {/* ... */},
};
return (
<AuthStateContext.Provider value={value}>
<AuthActionsContext.Provider value={actions}>
{children}
</AuthActionsContext.Provider>
</AuthStateContext.Provider>
);
}
// Компоненты подписываются только на то, что нужно
function LoginForm() {
const { login } = useContext(AuthActionsContext); // Не перерендерится при изменении user
return <form onSubmit={() => login()}>...</form>;
}
Заключение
Context API — это мощный инструмент для избежания prop drilling и управления глобальным состоянием. Используй его для:
- Авторизации
- Темы
- Языковых настроек
- Уведомлений
- Конфигурации приложения
Для сложного state management (часто меняющиеся данные, нормализация, временная шкала) используй Redux или Zustand.