طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 242 · أطر الويب
الدرس 5 · الوحدة 2: إتقان إدارة الحالة

رفع الحالة ومشاركتها: Context

كيف تتشارك المكوّنات البعيدة البيانات نفسها؟ ⏱ نحو 30 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • ترفع الحالة إلى الأب المشترك وتمرر دوال التغيير للأبناء.
  • تشرح تدفق البيانات في اتجاه واحد.
  • تحل مشكلة حفر الخصائص بـ Context.
  • تبني مزوّدًا بقيمة ودوال مع Hook مخصص.

١حين يحتاج مكوّنان الحالة نفسها

في متجر: قائمة المنتجات فيها زر «أضف للسلة»، وفي الرأس عدّاد السلة. كلاهما يحتاج السلة نفسها. لو وضعنا الحالة في أحدهما لما رآها الآخر، لأن البيانات تتدفق من الأب إلى الابن فقط.

الحل اسمه رفع الحالة Lifting state up: ننقل الحالة إلى أقرب أب مشترك، ثم نمرر القيمة للأبناء خصائص، ونمرر دالة التغيير لمن يحتاج أن يغيّرها:

import { useState } from 'react';import Header from './Header.jsx';import ProductList from './ProductList.jsx'; export default function App() {  const [cart, setCart] = useState([]);           // الحالة في الأب المشترك   function addToCart(product) {    setCart([...cart, product]);  }   return (    <>      <Header count={cart.length} />      <ProductList onAdd={addToCart} />    </>  );}

الابن ProductList لا يعرف كيف تُحفظ السلة؛ هو فقط يستدعي onAdd التي وصلته. هكذا «يرسل» الابن حدثًا إلى أعلى، وتبقى الحالة في مكان واحد. هذا هو تدفق البيانات في اتجاه واحد: البيانات تنزل خصائص، والأحداث تصعد دوالًّا.

٢مشكلة «حفر الخصائص»

حين تكبر الشجرة، قد تحتاج قيمة في مكوّن بعيد جدًّا (مثل المستخدم الحالي، أو اللغة، أو الوضع الداكن)، فنمررها عبر مكوّنات وسيطة لا تستخدمها أصلًا. هذا اسمه حفر الخصائص Prop drilling:

<App user={user}>  <Layout user={user}>          // لا يستخدمه، يمرّره فقط    <Sidebar user={user}>       // لا يستخدمه، يمرّره فقط      <Avatar user={user} />    // أخيرًا يستخدمه!

والحل في React: السياق Context. نضع القيمة في «مزوّد» Provider أعلى الشجرة، فيقرؤها أي مكوّن تحته مباشرة بـ useContext، دون المرور بالوسطاء.

٣Context خطوة بخطوة

  1. أنشئ السياق: const ThemeContext = createContext('light')
  2. غلّف الشجرة بالمزوّد وأعطه القيمة: <ThemeContext.Provider value={{theme}}>
  3. اقرأ القيمة في أي مكوّن تحته: const theme = useContext(ThemeContext)
import { useState } from 'react';import { ThemeContext } from './theme.js';import Page from './Page.jsx'; export default function App() {  const [theme, setTheme] = useState('light');  return (    <ThemeContext.Provider value={theme}>      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>        {theme === 'light' ? '🌙 الوضع الداكن' : '☀️ الوضع الفاتح'}      </button>      <Page />    </ThemeContext.Provider>  );}

٤سياق بقيمة ودوال: مزوّد السلة

نمط شائع جدًّا: مكوّن «مزوّد» يملك الحالة ودوالها، ويضعها كلها في السياق، ومعه Hook صغير يسهّل القراءة. بعدها أي مكوّن يقرأ السلة أو يضيف إليها بسطر واحد:

import { CartProvider } from './CartContext.jsx';import Header from './Header.jsx';import Products from './Products.jsx'; export default function App() {  return (    <CartProvider>      <Header />      <Products />    </CartProvider>  );}
رفع الحالة والخصائصContext
المناسبمكوّنان أو ثلاثة متقاربةقيم يحتاجها كثيرون في أماكن بعيدة
أمثلةحقل بحث وقائمة نتائجالمستخدم الحالي، واللغة، والوضع الداكن، والسلة
الوضوحمصدر البيانات ظاهر في الخصائصأخفى، فلا تستخدمه لكل شيء

توقّع وتحقّق

مكوّنان شقيقان يحتاجان الحالة نفسها. أين نضعها؟

رفع الحالة.

كيف يغيّر الابن حالة يملكها الأب؟

الأحداث تصعد دوالًّا.

ما المشكلة التي يحلها Context؟

Prop drilling.

لقراءة قيمة سياق داخل مكوّن نستخدم:

تحت المزوّد.

الخلاصة

تحدٍّ برمجي: تبديل اللغة للتطبيق كله

متوسط

تطبيق صغير بلغتين. زر في الأعلى يبدّل اللغة، ومكوّن بعيد يقرؤها من السياق.

المطلوب:
  • أنشئ LangContext بقيمة افتراضية 'ar'.
  • App يملك حالة اللغة، ويضع المزوّد، وفيه زر بالمعرّف id="switch" يبدّل بين ar و en.
  • المكوّن Welcome (داخل Main الذي لا يستقبل أي خاصية) يقرأ اللغة بـ useContext ويعرض <h1>: «مرحبًا» أو «Welcome».
  • لا تمرر اللغة خاصيةً إلى Main.
👀 اعرض الناتج المطلوب (وجرّبه)
✓ التحقق الآلي من حلّك:
  • الكود يعمل بلا أخطاء في Console
  • في البداية: مرحبًا
  • الزر يبدّل إلى Welcome
  • ضغطة أخرى تعيده إلى مرحبًا
  • تستخدم createContext و useContext
  • Main لا يستقبل خصائص

💡 تلميح 1
انظر القسم 3: createContext ثم Provider ثم useContext.
💡 تلميح 2
Main يبقى function Main() بلا معاملات.

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

import { useState, createContext, useContext } from 'react'; const LangContext = createContext('ar'); function Welcome() {  const lang = useContext(LangContext);  return <h1>{lang === 'ar' ? 'مرحبًا' : 'Welcome'}</h1>;} function Main() {  return <main><Welcome /></main>;} export default function App() {  const [lang, setLang] = useState('ar');  return (    <LangContext.Provider value={lang}>      <button id="switch" onClick={() => setLang(lang === 'ar' ? 'en' : 'ar')}>        {lang === 'ar' ? 'English' : 'العربية'}      </button>      <Main />    </LangContext.Provider>  );}
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس