React: السياق والحالة العامة

آخر تحديث: 2026-10-26

1. ما ستتعلمه



2. قصة حول تغيير السمات

(1) المشكلة: مرور الدعائم عبر 5 مستويات من المكونات

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

▶ مثال: استخدام السياق

الناتج:

TEXT
الحالة القائمة على Context مشتركة بين المكونات
▶ جرّب الكود

الناتج:

TEXT
حالة مكون App هي 'light'. يعرض واجهة تفاعلية.

سؤال أليس:

(2) حل مسألة «السياق»

▶ جرّب الكود

المزايا: لم تعد المكونات الوسيطة (Page، MainLayout، Sidebar) بحاجة إلى تمرير خصائص theme؛ حيث يتم تمرير البيانات مباشرةً من «المزود» إلى «المستهلك».



3. المفاهيم الأساسية للسياق

واجهة برمجة التطبيقات (API) الدالة المعلمات/القيم المرجعة
createContext(defaultValue) إنشاء حاوية سياق العودة إلى { Provider, Consumer }
<Context.Provider value={...}> يوفر البيانات للشجرة الفرعية value: أي نوع؛ يؤدي إلى إعادة عرض «المستهلك» عند تغير قيمته
useContext(Context) استرداد أحدث بيانات المزود إرجاع value الخاص بالمزود؛ وفي حالة عدم العثور على مزود، يتم إرجاع القيمة الافتراضية
مكون مخصص useXxx() يغلف useContext + فحص الأخطاء النمط الموصى به: يطلق خطأً واضحًا في حالة عدم وجود Provider

(1) createContext + Provider + useContext

100%
graph TB
    A[createContext<br/>Create a context container] --> B[Provider<br/>Wrapper Component tree<br/>Provide value]
    B --> C[Middle Component<br/>Don't care Context]
    C --> D[useContext<br/>Consumption Context Data]
    
    style A fill:#1890ff,color:#fff
    style B fill:#52c41a,color:#fff
    style D fill:#ff6b6b,color:#fff
▶ جرّب الكود

(2) الحالات التي ينطبق فيها السياق

مناسب للسياق غير مناسب للسياق
السمة (الوضع الداكن) معلمات تكوين المكونات (باستخدام Props)
معلومات تسجيل دخول المستخدم قيم الإدخال في النموذج (باستخدام الحالة المحلية)
اللغة/التدويل بيانات واجهة برمجة التطبيقات (باستخدام TanStack Query)
حالة المسار حالة الرسوم المتحركة (تديرها مكتبة الرسوم المتحركة)
الإعدادات العامة التمرير البسيط للخصائص من العنصر الأصلي إلى العنصر التابع (باستخدام الخصائص مباشرةً)

القاعدة: استخدم «Context» فقط عندما تكون هناك حاجة لمشاركة البيانات عبر «مستويات متعددة ومكونات متعددة». أما إذا كنت تقوم ببساطة بتمرير القيم بين المكونات الأم والمكونات التابعة، فإن «Props» تكفي.



4. تداخل السياقات وتقسيم المكونات إلى مزودات

(1) تداخل السياقات متعدد المستويات

عادةً ما تتضمن المشاريع الواقعية سياقات متعددة:

JSX
function App() {
  return (
    <AuthProvider>
      <ThemeProvider>
        <I18nProvider>
          <NotificationProvider>
            <MainApp />
          </NotificationProvider>
        </I18nProvider>
      </ThemeProvider>
    </AuthProvider>
  )
}
▶ جرّب الكود

لتجنب «جحيم التداخل»، يمكنك إنشاء مكون مركب AppProviders:

JSX
function AppProviders({ children }) {
  return (
    <AuthProvider>
      <ThemeProvider>
        <I18nProvider>
          <NotificationProvider>
            {children}
          </NotificationProvider>
        </I18nProvider>
      </ThemeProvider>
    </AuthProvider>
  )
}

function App() {
  return (
    <AppProviders>
      <MainApp />
    </AppProviders>
  )
}
▶ جرّب الكود

(2) ربط مزود مخصص

▶ جرّب الكود

5. تحسين الأداء حسب السياق

عندما تتغير قيمة «السياق»، يتم إعادة عرض جميع المكونات التي تستخدم هذا «السياق». وهذا هو أكبر عائق يواجه أداء «السياقات».

طريقة التحسين المبدأ السيناريوهات القابلة للتطبيق
useMemo قيمة ثابتة تجنب إنشاء مراجع كائنات جديدة عند كل عملية عرض عندما تحتوي قيمة المزود على دالة أو كائن
التقسيم حسب السياق تُخصص البيانات «المتغيرة» و«الثابتة» لسياقات مختلفة لا يحتاج بعض المستهلكين سوى إلى بيانات مستقرة
React.memo مكون Consumer المُغلف تخطي إعادة العرض عندما لا تتغير الخصائص عندما يتلقى مكون Consumer خصائص أخرى
الاشتراك الانتقائي استخدم أداة التحديد لاسترجاع الشرائح التي تحتاجها فقط تنفيذ تحديثات دقيقة باستخدام Zustand/Redux
JSX
// ❌ Question:Every time App Rendering,value They're all new partners. → All Consumer All are heavily rendered
function App() {
  const [count, setCount] = useState(0)
  return (
    <ThemeContext.Provider value={{ theme: 'dark', setCount }}>
      <ExpensiveTree />
    </ThemeContext.Provider>
  )
}

// ✅ Fix 1:use  useMemo Stable value Quote
function App() {
  const [count, setCount] = useState(0)
  const themeValue = useMemo(
    () => ({ theme: 'dark', toggleTheme: () => setCount(c => c + 1) }),
    []
  )
  return (
    <ThemeContext.Provider value={themeValue}>
      <ExpensiveTree />
    </ThemeContext.Provider>
  )
}

// ✅ Fix 2:separate "unchanged" and "changed" data into different Context
const SettingsContext = createContext({ theme: 'light', language: 'zh' })
const CountContext = createContext(0)

function App() {
  const [count, setCount] = useState(0)
  return (
    <SettingsContext.Provider value={settingsValue}> {/* useMemo Stable */}
      <CountContext.Provider value={count}>
        <ExpensiveTree />
      </CountContext.Provider>
    </SettingsContext.Provider>
  )
}
// Only when used CountContext The component will be count Re-render on changes
// SettingsContext Consumers will not be affected
▶ جرّب الكود

6. Context + useReducer: إنشاء حالة عامة خفيفة الوزن

من خلال الجمع بين «الانتشار عبر المستويات» في Context و«إدارة الحالة المعقدة» في useReducer، يمكنك إنشاء حل خفيف الوزن لإدارة الحالة العامة:

▶ عرض المثال القابل للتشغيل (125 سطرًا)
▶ جرّب الكود

الناتج:

TEXT
يعرض: "Button". الحالة: theme (setter: setTheme). زر: Button

النتيجة المتوقعة: نظام إشعارات متكامل. يظهر في الجزء العلوي جرس إشعارات (يعرض عدد الإشعارات غير المقروءة)؛ وفي الوسط، يمكن للمستخدمين إرسال ثلاثة أنواع من الإشعارات: إشعارات المعلومات، وإشعارات النجاح، وإشعارات الخطأ؛ وفي الجزء السفلي، تُعرض قائمة بالإشعارات، حيث يمكن وضع علامة «مقروء» على كل إشعار أو حذفه.


▶ المثال 2: إدارة حالة مصادقة المستخدم (السياق + ربط مخصص)

▶ عرض المثال القابل للتشغيل (114 سطرًا)
▶ جرّب الكود

الناتج:

TEXT
نموذج تسجيل دخول بالبريد الإلكتروني/كلمة المرور. تنسيق بريد غير صالح → إطار أحمر + "The email address format is incorrect.". كلمة مرور أقل من 6 محارف → "Password must be at least 6 chars". الإرسال → console.log({email, password})

الناتج:

TEXT
العنوان الفرعي: "🔐 Log In". يعرض: "🔐 Log In". الحالة: user (setter: setUser)، loading (setter: setLoading)، username (setter: setUsername). الأزرار: {loading ? 'Logging in......' : 'Log In'}، Log Out. الإدخال: Username (Input admin Experience the Role of an Administrator)، Password. نموذج مع معالجة للإرسال. يحسّن useMemo حساب الرسم. يُخزن useCallback مرجع دالة المعالجة. يوفر Context حالة عامة. جلب بيانات غير متزامن/حالة التحميل. سلوك قائم على مؤقت

▶ المثال 3: دمج طبقات مزودين متعددة مع التوطين الدولي (i18n)

▶ جرّب الكود

الناتج:

TEXT
التدويل: مبدل اللغة يغير كل النصوص. نمط Context + provider يدعم تعدد اللغات

الناتج:

TEXT
العنوان الفرعي: "{t('greeting')}". يعرض: "Chinese". الحالة: locale (setter: setLocale). القائمة المنسدلة: Chinese، English. يحسّن useMemo حساب الرسم. يُخزن useCallback مرجع دالة المعالجة. يوفر Context حالة عامة

▶ المثال 4: تقسيم السياق لتحسين الأداء

▶ جرّب الكود

الناتج:

TEXT
حالة مكون SplitContextApp هي: null، false. يعرض واجهة تفاعلية.

الناتج:

TEXT
يعرض: "Login". الحالة: user (setter: setUser)، loading (setter: setLoading). زر: login('Alice')}>Login. يحسّن useMemo حساب الرسم. يوفر Context حالة عامة. جلب بيانات غير متزامن/حالة التحميل. سلوك قائم على مؤقت

▶ المثال 5: مثال كامل على استخدام useContext كبديل عن تدريبات «Props Drilling»

▶ جرّب الكود

الناتج:

TEXT
حالة مكون SettingsApp هي: {
    theme: 'light', fontSize: 14, sidebarCollapsed: false,
  }. يعرض واجهة تفاعلية.


❓ أسئلة شائعة

س ماذا أفعل إذا كان value الخاص بـ Context يمثل كائنًا جديدًا في كل مرة، مما يتسبب في إعادة عرض جميع Consumer؟
ج استخدم useMemo لتثبيت مرجع value: const value = useMemo(() => ({ theme, toggleTheme }), [theme]). يمكنك أيضًا فصل البيانات «الثابتة» و«المتغيرة» إلى Context مختلفة. إذا استمرت مشكلات الأداء بعد التحسين، ففكر في استخدام Zustand (الذي يدعم التحديثات الدقيقة ولن يؤدي إلى إعادة عرض المكونات غير ذات الصلة).
س ماذا أفعل إذا كان هناك عدد كبير جدًّا من طبقات السياقات المتداخلة؟
ج ① استخدم «مزودًا» (Provider) لتجميع المكونات (مثل AppProviders المذكور سابقًا) وتغليف التداخل؛ ② فكر فيما إذا كنت بحاجة فعلاً إلى هذا العدد الكبير من السياقات — فبعض البيانات «العامة» هي في الواقع مجرد بيانات «عامة محلياً» (مثل إعدادات صفحة معينة) ولا تحتاج إلى وضعها في سياق المستوى الأعلى للتطبيق.
س لماذا نتحقق مما إذا كان context قيمة فارغة في خطاف useXxx المخصص؟
ج هذه تقنية برمجة وقائية. إذا نسي المطور تغليف مكون بـ Provider قبل استخدام useContext، فسيكون context فارغًا (إذا لم يتم تمرير قيمة افتراضية إلى createContext) أو سيتسبب في أخطاء يصعب تصحيحها. إن إظهار رسالة خطأ واضحة في وقت مبكر يعد أكثر سهولة للمستخدم بكثير من أن يرى المستخدم عبارة «لا يمكن قراءة خاصية null» في وحدة التحكم بالمتصفح.
س هل يمكن لـ Context أن يحل محل Redux؟
ج يمكنه ذلك في المشاريع الصغيرة والمتوسطة الحجم، ولكنه لا يصلح للمشاريع الكبيرة. يفتقر Context إلى ثلاث قدرات تتوفر في Redux: ① آلية البرمجيات الوسيطة (التسجيل، والعمليات غير المتزامنة، والاستمرارية)؛ ② تصحيح الأخطاء عبر «السفر عبر الزمن» (إعادة تشغيل تغييرات الحالة في DevTools)؛ ③ الاشتراك الانتقائي (يؤدي أي تغيير في قيمة Context إلى إعادة عرض جميع المستهلكين، بينما يتيح لك Redux استخدام المحددات للاشتراك فقط في الأجزاء المحددة التي تحتاجها). إذا كان مشروعك يتطلب هذه القدرات الثلاث، فاستخدم Zustand (خفيف الوزن) أو Redux Toolkit.

📖 ملخص


📝 تمارين

  1. المشكلة الأساسية (صعوبة ⭐): قم بإنشاء ربط LocaleProvider + useLocale يوفر طريقتي locale (zh/en) وtoggleLocale() لتمكين التبديل بين اللغتين الصينية والإنجليزية.
  2. تمرين متقدم (مستوى الصعوبة ⭐⭐): قم بإنشاء هوك AuthProvider + useAuth يوفر الطرق user وlogin(username, password) وlogout() لإدارة حالة مصادقة المستخدم.
  3. التحدي (الصعوبة: ⭐⭐⭐): أنشئ هوكًا باستخدام CartProvider + useCart واستخدم Context + useReducer لتنفيذ عربة تسوق مزودة بوظائف لإضافة العناصر وإزالتها وتغيير الكميات ومسح محتويات العربة. استخدم useMemo لتحسين الكود وتجنب إعادة العرض غير الضرورية.
Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%