React: السياق والحالة العامة
آخر تحديث: 2026-10-26
1. ما ستتعلمه
- createContext و Provider
- useContext: سياق الاستهلاك
- التدريب على السياق مقابل التدريب على العناصر المسرحية
- التداخل متعدد المستويات وتحسين الأداء
- الجمع بين Context و useReducer (إدارة الحالة الخفيفة)
2. قصة حول تغيير السمات
(1) المشكلة: مرور الدعائم عبر 5 مستويات من المكونات
تريد أليس إضافة ميزة التبديل بين «الوضع الداكن» و«الوضع الفاتح» إلى التطبيق. ويجب أن يمر تدفق البيانات عبر خمس طبقات من المكونات:
▶ مثال: استخدام السياق
الناتج:
الحالة القائمة على Context مشتركة بين المكونات
الناتج:
حالة مكون App هي 'light'. يعرض واجهة تفاعلية.
سؤال أليس:
themeمع مرور البيانات من التطبيق عبر كل طبقة وصولاً إلى الزر، فإن الطبقات الثلاث الوسيطة لا تحتاج في الواقع إلى هذه البيانات- إذا أضفنا معلومة أخرى عن المستخدم لنقوم بتمريرها، فستصبح قائمة «props» متضخمة للغاية.
- هذا هو تدريب استخدام الدعائم الكلاسيكي
(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
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) تداخل السياقات متعدد المستويات
عادةً ما تتضمن المشاريع الواقعية سياقات متعددة:
function App() {
return (
<AuthProvider>
<ThemeProvider>
<I18nProvider>
<NotificationProvider>
<MainApp />
</NotificationProvider>
</I18nProvider>
</ThemeProvider>
</AuthProvider>
)
}
لتجنب «جحيم التداخل»، يمكنك إنشاء مكون مركب AppProviders:
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 |
// ❌ 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 سطرًا)
الناتج:
يعرض: "Button". الحالة: theme (setter: setTheme). زر: Button
النتيجة المتوقعة: نظام إشعارات متكامل. يظهر في الجزء العلوي جرس إشعارات (يعرض عدد الإشعارات غير المقروءة)؛ وفي الوسط، يمكن للمستخدمين إرسال ثلاثة أنواع من الإشعارات: إشعارات المعلومات، وإشعارات النجاح، وإشعارات الخطأ؛ وفي الجزء السفلي، تُعرض قائمة بالإشعارات، حيث يمكن وضع علامة «مقروء» على كل إشعار أو حذفه.
▶ المثال 2: إدارة حالة مصادقة المستخدم (السياق + ربط مخصص)
▶ عرض المثال القابل للتشغيل (114 سطرًا)
الناتج:
نموذج تسجيل دخول بالبريد الإلكتروني/كلمة المرور. تنسيق بريد غير صالح → إطار أحمر + "The email address format is incorrect.". كلمة مرور أقل من 6 محارف → "Password must be at least 6 chars". الإرسال → console.log({email, password})
الناتج:
العنوان الفرعي: "🔐 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)
▶ جرّب الكودالناتج:
التدويل: مبدل اللغة يغير كل النصوص. نمط Context + provider يدعم تعدد اللغات
الناتج:
العنوان الفرعي: "{t('greeting')}". يعرض: "Chinese". الحالة: locale (setter: setLocale). القائمة المنسدلة: Chinese، English. يحسّن useMemo حساب الرسم. يُخزن useCallback مرجع دالة المعالجة. يوفر Context حالة عامة
▶ المثال 4: تقسيم السياق لتحسين الأداء
▶ جرّب الكودالناتج:
حالة مكون SplitContextApp هي: null، false. يعرض واجهة تفاعلية.
الناتج:
يعرض: "Login". الحالة: user (setter: setUser)، loading (setter: setLoading). زر: login('Alice')}>Login. يحسّن useMemo حساب الرسم. يوفر Context حالة عامة. جلب بيانات غير متزامن/حالة التحميل. سلوك قائم على مؤقت
▶ المثال 5: مثال كامل على استخدام useContext كبديل عن تدريبات «Props Drilling»
▶ جرّب الكود
الناتج:
حالة مكون SettingsApp هي: {
theme: 'light', fontSize: 14, sidebarCollapsed: false,
}. يعرض واجهة تفاعلية.
❓ أسئلة شائعة
value الخاص بـ Context يمثل كائنًا جديدًا في كل مرة، مما يتسبب في إعادة عرض جميع Consumer؟useMemo لتثبيت مرجع value: const value = useMemo(() => ({ theme, toggleTheme }), [theme]). يمكنك أيضًا فصل البيانات «الثابتة» و«المتغيرة» إلى Context مختلفة. إذا استمرت مشكلات الأداء بعد التحسين، ففكر في استخدام Zustand (الذي يدعم التحديثات الدقيقة ولن يؤدي إلى إعادة عرض المكونات غير ذات الصلة).AppProviders المذكور سابقًا) وتغليف التداخل؛ ② فكر فيما إذا كنت بحاجة فعلاً إلى هذا العدد الكبير من السياقات — فبعض البيانات «العامة» هي في الواقع مجرد بيانات «عامة محلياً» (مثل إعدادات صفحة معينة) ولا تحتاج إلى وضعها في سياق المستوى الأعلى للتطبيق.context قيمة فارغة في خطاف useXxx المخصص؟Provider قبل استخدام useContext، فسيكون context فارغًا (إذا لم يتم تمرير قيمة افتراضية إلى createContext) أو سيتسبب في أخطاء يصعب تصحيحها. إن إظهار رسالة خطأ واضحة في وقت مبكر يعد أكثر سهولة للمستخدم بكثير من أن يرى المستخدم عبارة «لا يمكن قراءة خاصية null» في وحدة التحكم بالمتصفح.📖 ملخص
createContext→.Provider→useContext3 خطوات لاستخدام Context- يحل Context مشكلة Props Drilling، لكنه لا يحل مشكلة «إدارة الحالة»
- مشكلة في الأداء: تؤدي التغييرات في قيمة السياق إلى إعادة عرض جميع العناصر المستهلكة؛ قم بالتحسين باستخدام
useMemo - يُعد تخصيص الـ
useXxx+ فحص الأخطاء من أفضل الممارسات الموصى بها - يمكن أن يُستخدم
Context + useReducerكحل خفيف الوزن لإدارة الحالة العالمية، ولكن في الحالات المعقدة، لا يزال من الأفضل استخدام Zustand أو Redux.
📝 تمارين
- المشكلة الأساسية (صعوبة ⭐): قم بإنشاء ربط
LocaleProvider+useLocaleيوفر طريقتيlocale(zh/en) وtoggleLocale()لتمكين التبديل بين اللغتين الصينية والإنجليزية. - تمرين متقدم (مستوى الصعوبة ⭐⭐): قم بإنشاء هوك
AuthProvider+useAuthيوفر الطرقuserوlogin(username, password)وlogout()لإدارة حالة مصادقة المستخدم. - التحدي (الصعوبة: ⭐⭐⭐): أنشئ هوكًا باستخدام
CartProvider+useCartواستخدم Context + useReducer لتنفيذ عربة تسوق مزودة بوظائف لإضافة العناصر وإزالتها وتغيير الكميات ومسح محتويات العربة. استخدمuseMemoلتحسين الكود وتجنب إعادة العرض غير الضرورية.