يقرأ useContext قيمة يوفرها مكوّن أب، فيستطيع مكوّن في عمق الشجرة الحصول عليها دون أن يمررها كل مكوّن بينهما كـ prop. تنشئ سياقًا بـ createContext، وتغلّف جزءًا من الشجرة بمزوّد له value، وتستدعي useContext(TheContext) حيثما احتجت إلى تلك القيمة.
لا يذكر Toolbar السمة أبدًا، ومع ذلك يتغير الزران كلاهما عندما تحدد المربع. هذه هي فكرة السياق كلها: لا تحمل المكوّنات الوسيطة prop لا تستخدمها، وهي مشكلة تمرير props عبر الطبقات التي يحلها السياق.
الخطوات الثلاث
// 1. Create it once, outside any component, usually in its own file
export const ThemeContext = createContext('light');
// 2. Provide a value to a part of the tree
<ThemeContext value={theme}>
<Page />
</ThemeContext>
// 3. Read it in any component inside that part
const theme = useContext(ThemeContext);
في React 19 يكون كائن السياق نفسه هو المزوّد. ويكتب الكود الأقدم <ThemeContext.Provider value={theme}>، الذي ما زال يعمل، فسترى الشكلين.
يبحث useContext إلى أعلى الشجرة عن أقرب مزوّد لذلك السياق. يقرأ المكوّن الأقرب، فيمكنك تداخل المزوّدين لتجاوز قيمة في قسم واحد.
القيمة الافتراضية
وسيط createContext هو ما يُرجعه useContext عندما لا يوجد مزوّد فوق المكوّن. لا يتغير عندما تتغير الحالة؛ إنه قيمة احتياطية.
ليس فوق Greeting الأول أي مزوّد فيحصل على القيمة الافتراضية 'en'. ويقرأ الثاني 'es'، ويقرأ الثالث القيمة الأقرب 'ja'. غيّر value="es" الخارجية إلى value="ja" فلا يتغير إلا السطر الثاني.
تحديث السياق من ابن
لا يمرر السياق إلا قيمة إلى الأسفل. ولتمكين ابن عميق من تغييرها، احتفظ بالقيمة في الحالة حيث يوجد المزوّد، وضع دالة الضبط في السياق بجانبها.
يستدعي LoginButton الدالة setUser، فتتغير الحالة في App، ويحصل المزوّد على قيمة جديدة، ويتحدث Header أيضًا. لم يستقبل أي من المكوّنين prop.
كل مستهلك يُعاد عرضه عندما تتغير القيمة
عندما تتغير value لمزوّد، تعرض React كل مكوّن يستدعي useContext له، حتى لو كان ذلك المكوّن داخل memo وبقيت props الخاصة به نفسها. تقارن React القيمة القديمة بالجديدة باستخدام Object.is. لذا فإن value={{ user, setUser }} كائن جديد في كل عرض لـ App، فيُعاد عرض كل مستهلك حتى عندما لا تتغير user.
يسجّل القارئان مرة في العرض الأول. انقر الزر: لا يسجّل من جديد إلا PlainReader، لأن سياقه حصل على كائن جديد. يسلّم useMemo إلى MemoContext الكائن نفسه حتى تتغير name، فيبقى MemoReader ثابتًا. لا يهم هذا إلا عندما يصبح العرض بطيئًا؛ أما في تطبيق صغير فلا بأس بكائن جديد في كل عرض.
تقسيم السياقات
ينبغي ألا تتشارك قيمة تتغير كثيرًا وقيمة لا تتغير أبدًا سياقًا واحدًا. إذا كانت دالة الضبط مع الحالة، فإن المكوّن الذي يحتاج فقط إلى استدعاء setUser يظل يُعاد عرضه في كل مرة تتغير فيها user. ضعهما في سياقين فيشترك كل مستهلك فقط في ما يقرؤه:
const UserContext = createContext(null);
const SetUserContext = createContext(() => {});
function UserProvider({ children }) {
const [user, setUser] = useState(null);
return (
<SetUserContext value={setUser}>
<UserContext value={user}>{children}</UserContext>
</SetUserContext>
);
}
تحتفظ setUser المأخوذة من useState بالهوية نفسها إلى الأبد، فالمكوّنات التي لا تقرأ إلا SetUserContext لا يُعاد عرضها أبدًا بسبب تسجيل دخول. والفكرة نفسها تنطبق على البيانات غير المترابطة: السمة وسلة المشتريات مكانهما سياقان منفصلان، لا AppContext كبير واحد.
ومن اللمسات الأخيرة الشائعة خطاف مخصص مثل useUser() يستدعي useContext(UserContext) ويرمي خطأ واضحًا عندما يغيب المزوّد، فلا يستورد المستدعون كائن السياق مباشرة أبدًا.
السياق أم props أم مكتبة حالة
- props هي الخيار الافتراضي. تمرير قيمة مستويين أو ثلاثة إلى الأسفل واضح وسهل التتبع.
- السياق يناسب القيم التي تحتاجها مكوّنات كثيرة على أعماق كثيرة ونادرًا ما تتغير: السمة، واللغة، والمستخدم المسجّل، وكائن أعلام الميزات.
- مكتبة الحالة (Redux Toolkit وZustand وJotai وغيرها) تناسب الحالة الكبيرة التي تتحدث كثيرًا، حيث تريد أن يشترك مكوّن في جزء واحد ويتخطى مرات العرض التي يسببها الباقي. ليس للسياق محدد كهذا: يُعاد عرض المستهلك مع أي تغيير في القيمة.
قبل اللجوء إلى السياق أو إلى مكتبة، تحقق مما إذا كان تمرير المكوّنات على أنها children يزيل التمرير عبر الطبقات. كثيرًا ما يفعل، دون أي واجهة جديدة على الإطلاق.
الأسئلة الشائعة
ماذا يفعل useContext في React؟
يُرجع القيمة الحالية لسياق ما، مأخوذة من أقرب مزوّد فوق المكوّن في الشجرة. وعندما تتغير قيمة ذلك المزوّد، يُعاد عرض كل مكوّن يقرؤها.
ما فائدة القيمة الافتراضية في createContext؟
هي ما يُرجعه useContext عندما لا يوجد مزوّد فوق المكوّن. وهي مفيدة للاختبارات وللمكوّنات المعروضة وحدها، ولا تتغير أبدًا.
هل ما زلت أحتاج إلى Context.Provider في React 19؟
لا. في React 19 يمكنك عرض السياق نفسه كمزوّد: <ThemeContext value="dark">. وما زال <ThemeContext.Provider value="dark"> يعمل، فيستمر الكود الأقدم في العمل.
كيف أحدّث قيمة سياق من مكوّن ابن؟
احتفظ بالقيمة في الحالة داخل المكوّن الذي يعرض المزوّد، وضع دالة الضبط في السياق بجانب القيمة: value={{ theme, setTheme }}. يقرأ الابن setTheme بـ useContext ويستدعيها.
هل سياق React بديل عن Redux؟
يمرر السياق قيمة إلى أسفل الشجرة؛ ولا يدير الحالة بنفسه. ومع useState أو useReducer يغطي تطبيقات كثيرة. أما مكتبة الحالة فتضيف ما ينقص السياق، مثل الاشتراك في جزء واحد فقط حتى لا تعيد التحديثات غير المرتبطة عرض مكوّن، إضافة إلى أدوات التطوير والبرمجيات الوسيطة.