المشكلة: تمرير الخصائص العميق (Prop Drilling)
حين تحتاج بيانات (مثل المستخدم أو الثيم) في مكوّنات عميقة، تمرّرها عبر props خلال كل مستوى — حتى المكوّنات التي لا تحتاجها. هذا مرهق. الحلّ: Context.
ثلاث خطوات لاستخدام Context
1) إنشاء السياق
import { createContext } from "react";
export const ThemeContext = createContext();
2) التزويد بالقيمة (Provider)
غلّف الجزء الذي يحتاج البيانات:
function App() {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Page />
</ThemeContext.Provider>
);
}
3) الاستهلاك بـ useContext
أي مكوّن داخل الـ Provider يصل للقيمة مباشرةً:
import { useContext } from "react";
function ThemeButton() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === "light" ? "dark" : "light")}>
الوضع الحالي: {theme}
</button>
);
}
لا حاجة لتمرير theme عبر كل المستويات!
مثال متكامل: سياق المستخدم
const UserContext = createContext();
function App() {
const [user, setUser] = useState({ name: "براء" });
return (
<UserContext.Provider value={user}>
<Navbar />
</UserContext.Provider>
);
}
function Navbar() {
const user = useContext(UserContext);
return <p>أهلًا {user.name}</p>;
}
متى تستخدم Context؟
مثالي للبيانات العامّة التي يحتاجها كثيرون: الثيم، المستخدم المسجَّل، اللغة، سلة التسوّق.
⚠️ ليس بديلًا عن كل props. للبيانات المحلّية بين مكوّنين، يبقى تمرير props أبسط. وللحالة المعقّدة الضخمة، فكّر في مكتبات إدارة الحالة (Redux, Zustand).
نمط متقدم: Context + Repository
عندما يحتاج الـ Context إلى جلب بيانات (مثل المستخدم الحالي)، لا تضع fetch داخل الـ Provider مباشرة. استخدم Repository Pattern:
import { createContext, useContext, useState, ReactNode } from "react";
import { HttpClient, UserRepository, HttpUserRepository, ApiError, NetworkError } from "./lib/react-base-repository";
// Repository — الطبقة الوحيدة التي تتصل بالخادم
const http = new HttpClient("/api");
const authRepo: UserRepository = new HttpUserRepository(http);
// Service Interface
interface AuthContextType {
user: UserDTO | null;
login(email: string, password: string): Promise<void>;
logout(): void;
}
const AuthContext = createContext<AuthContextType | null>(null);
// Provider — يستخدم Repository، لا يستدعي fetch مباشرة
function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<UserDTO | null>(null);
async function login(email: string, password: string) {
try {
const loggedInUser = await authRepo.createUser({ name: email, email, isActive: true });
setUser(loggedInUser);
} catch (err) {
if (err instanceof ApiError) throw new Error(err.message);
if (err instanceof NetworkError) throw new Error("تعذّر الاتصال");
throw err;
}
}
function logout() { setUser(null); }
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
// Custom Hook — Service Layer (المكوّنات تستخدم هذا فقط)
function useAuth(): AuthContextType {
const ctx = useContext(AuthContext);
if (!ctx) throw new Error("useAuth must be used within AuthProvider");
return ctx;
}
// المكوّن — لا يعرف Repository أو HTTP
function LoginButton() {
const { login } = useAuth();
return <button onClick={() => login("a@b.com", "pw").catch(alert)}>دخول</button>;
}
الأخطاء الشائعة
- ❌ وضع كل شيء في Context → يصعّب التتبّع ويُعيد عرض الكثير.
- ❌ استهلاك السياق خارج الـ Provider → القيمة تكون افتراضية/غير معرّفة.
- ❌ قيمة كائن جديدة في كل عرض → قد تسبّب إعادة عرض زائدة (استخدم useMemo عند الحاجة).
خلاصة
Context يشارك البيانات العامّة دون prop drilling: أنشئ بـ createContext، زوّد بـ .Provider value={...}، واستهلك بـ useContext. مثالي للثيم والمستخدم واللغة — حلٌّ أنيق لمشكلة تمرير الخصائص العميق.