Context API
מבוא
Context API הוא המנגנון המובנה של React לשיתוף נתונים "גלובליים" בתוך עץ הקומפוננטות, והפתרון הישיר לבעיית ה-Props Drilling. במקום להעביר prop יד ביד דרך כל רמת ביניים, מגדירים Context פעם אחת - וכל קומפוננטה בתוך העץ יכולה לגשת אליו ישירות. הוא מתאים במיוחד לנתונים שנצרכים במקומות רבים, כמו ערכת נושא, שפה או פרטי המשתמש המחובר.
ללא Context: עם Context:
App ──► Layout App (Provider)
──► Sidebar ├── Layout
──► UserCard ├── Sidebar
──► Avatar └── Avatar (Consumer)
↑ נגיש ישירות!
שלושת השלבים
העבודה עם Context בנויה תמיד משלושה שלבים: יוצרים את ה-Context, עוטפים את העץ ב-Provider שמספק את הערך, וצורכים את הערך בכל קומפוננטה שצריכה אותו.
1. Create - יצירת Context
import { createContext } from 'react';
// יצירת Context עם ערך ברירת מחדל
const ThemeContext = createContext('light');
// או בלי ערך ברירת מחדל
const UserContext = createContext(null);
2. Provide - הספקת הערך
import { ThemeContext } from './ThemeContext';
function App() {
const [theme, setTheme] = useState('dark');
return (
// Provider עוטף את כל הקומפוננטות שצריכות גישה
<ThemeContext.Provider value={theme}>
<Header />
<Main />
<Footer />
</ThemeContext.Provider>
);
}
3. Consume - צריכת הערך
import { useContext } from 'react';
import { ThemeContext } from './ThemeContext';
function ThemedButton() {
// שימוש ב-useContext לקבלת הערך
const theme = useContext(ThemeContext);
return (
<button className={`btn-${theme}`}>
כפתור ב-{theme} mode
</button>
);
}
דוגמה מלאה: Theme Context
קובץ ThemeContext.jsx
import { createContext, useContext, useState } from 'react';
// יצירת Context
const ThemeContext = createContext(null);
// Provider Component
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState('light');
const toggleTheme = () => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
};
// ערך ה-Context כולל את ה-state והפונקציות
const value = {
theme,
toggleTheme,
isDark: theme === 'dark'
};
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
// Custom Hook לשימוש נוח
export function useTheme() {
const context = useContext(ThemeContext);
if (context === null) {
throw new Error('useTheme must be used within ThemeProvider');
}
return context;
}
שימוש
// App.jsx
import { ThemeProvider } from './ThemeContext';
function App() {
return (
<ThemeProvider>
<Header />
<Main />
</ThemeProvider>
);
}
// Header.jsx
import { useTheme } from './ThemeContext';
function Header() {
const { theme, toggleTheme } = useTheme();
return (
<header className={theme}>
<button onClick={toggleTheme}>
{theme === 'light' ? '🌙' : '☀️'}
</button>
</header>
);
}
// ThemedCard.jsx
import { useTheme } from './ThemeContext';
function ThemedCard({ children }) {
const { isDark } = useTheme();
return (
<div style={{
backgroundColor: isDark ? '#333' : '#fff',
color: isDark ? '#fff' : '#333',
padding: '20px'
}}>
{children}
</div>
);
}
דוגמה: Auth Context
import { createContext, useContext, useState, useEffect } from 'react';
const AuthContext = createContext(null);
export function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// בדיקת משתמש מחובר בטעינה
const savedUser = localStorage.getItem('user');
if (savedUser) {
setUser(JSON.parse(savedUser));
}
setLoading(false);
}, []);
const login = async (email, password) => {
// קריאה ל-API
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ email, password })
});
const userData = await response.json();
setUser(userData);
localStorage.setItem('user', JSON.stringify(userData));
};
const logout = () => {
setUser(null);
localStorage.removeItem('user');
};
const value = {
user,
isAuthenticated: !!user,
loading,
login,
logout
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);
}
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth must be used within AuthProvider');
}
return context;
}
// שימוש בקומפוננטות
function LoginButton() {
const { isAuthenticated, logout } = useAuth();
if (isAuthenticated) {
return <button onClick={logout}>התנתק</button>;
}
return <a href="/login">התחבר</a>;
}
function ProtectedRoute({ children }) {
const { isAuthenticated, loading } = useAuth();
if (loading) return <p>טוען...</p>;
if (!isAuthenticated) return <Navigate to="/login" />;
return children;
}
שילוב מספר Contexts
function App() {
return (
<AuthProvider>
<ThemeProvider>
<LanguageProvider>
<Router>
<MainApp />
</Router>
</LanguageProvider>
</ThemeProvider>
</AuthProvider>
);
}
// קומפוננטה שמשתמשת במספר contexts
function Dashboard() {
const { user } = useAuth();
const { theme } = useTheme();
const { language } = useLanguage();
return (
<div className={theme}>
<h1>{language === 'he' ? 'שלום' : 'Hello'}, {user.name}</h1>
</div>
);
}
Context Performance
חשוב להכיר את מחיר הביצועים: בכל פעם שערך ה-Context משתנה, כל הקומפוננטות שצורכות אותו מתרנדרות מחדש. לכן כדאי לפצל Context לפי תחומים ולייצב את הערך שמעבירים, כפי שנראה בהמשך:
בעיה
// ❌ כל שינוי ב-count גורם לרינדור של כל הצרכנים
function AppProvider({ children }) {
const [user, setUser] = useState(null);
const [count, setCount] = useState(0);
return (
<AppContext.Provider value={{ user, setUser, count, setCount }}>
{children}
</AppContext.Provider>
);
}
פתרון: הפרדת Contexts
// ✅ הפרדה לפי תחום
const UserContext = createContext(null);
const CounterContext = createContext(null);
function Providers({ children }) {
return (
<UserProvider>
<CounterProvider>
{children}
</CounterProvider>
</UserProvider>
);
}
פתרון: useMemo
function AppProvider({ children }) {
const [user, setUser] = useState(null);
// מניעת יצירת אובייקט חדש בכל רינדור
const value = useMemo(() => ({ user, setUser }), [user]);
return (
<AppContext.Provider value={value}>
{children}
</AppContext.Provider>
);
}
Consumer (סגנון ישן)
לפני שהגיע ה-Hook בשם useContext, צרכו את ה-Context בעזרת רכיב Consumer ופונקציית render. התחביר עדיין נתמך, אך useContext קריא ונוח בהרבה ומומלץ לכל קוד חדש:
// סגנון ישן עם Consumer
<ThemeContext.Consumer>
{theme => (
<div className={theme}>תוכן</div>
)}
</ThemeContext.Consumer>
// סגנון מודרני עם useContext
function Component() {
const theme = useContext(ThemeContext);
return <div className={theme}>תוכן</div>;
}
סיכום
| פעולה | קוד |
|---|---|
| יצירה | const Ctx = createContext(default) |
| הספקה | <Ctx.Provider value={val}> |
| צריכה | const val = useContext(Ctx) |
| Custom Hook | useMyContext() עם בדיקת שגיאה |
הקודם: ← Component Patterns
הבא: Custom Hooks →
בדקו את עצמכם
נסו לענות לבד לפני שאתם פותחים את התשובה.
-
מתי כדאי להשתמש ב-Context?
הצגת התשובה
תשובה ג. Context פותר Props Drilling לנתונים גלובליים באמת.
-
מה החיסרון של ערך Context שמשתנה הרבה?
הצגת התשובה
תשובה ב. לכן מפצלים Contexts, ולא שמים שם state שמשתנה כל שנייה.