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

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

1. ما ستتعلمه



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

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

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

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

JSX
// Requirements:User switches to dark mode,All child components change accordingly

// Layer 5: innermost Button needs to Know theme
function Button({ theme }) {
  return <button style={{
    backgroundColor: theme === 'dark' ? '#333' : '#f0f0f0',
    color: theme === 'dark' ? 'white' : '#333'
  }}>Button</button>
}

// Layer 4
function Sidebar({ theme }) {
  return <div><Button theme={theme} /></div>
}

// Layer 3
function MainLayout({ theme }) {
  return <div><Sidebar theme={theme} /></div>
}

// Layer 2
function Page({ theme }) {
  return <MainLayout theme={theme} />
}

// Layer 1: App has theme state
function App() {
  const [theme, setTheme] = useState('light')
  return <Page theme={theme} />  // ❌ theme penetrates 5 layers
}
▶ جرّب الكود

سؤال أليس:

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

JSX
// 1. Create Context
const ThemeContext = React.createContext('light')

// 2. Provider Provide data
function App() {
  const [theme, setTheme] = useState('light')
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <Page />
    </ThemeContext.Provider>
  )
}

// The middle component is completely unconcerned with theme
function Page() { return <MainLayout /> }
function MainLayout() { return <Sidebar /> }
function Sidebar() { return <Button /> }

// 5. Only the necessary components are loaded Context
function Button() {
  const { theme, setTheme } = useContext(ThemeContext)
  return (
    <button style={{
      backgroundColor: theme === 'dark' ? '#333' : '#f0f0f0',
      color: theme === 'dark' ? 'white' : '#333'
    }}
    onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}>
      {theme === 'dark' ? '🌙' : '☀️'} Switch Themes
    </button>
  )
}
▶ جرّب الكود

المزايا: لم تعد المكونات الوسيطة (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
JSX
// 1. Create Context(You can specify a default value)
const UserContext = createContext({ name: 'Guest', role: 'guest' })
const ThemeContext = createContext('light')

// 2. Provider Provide data(The component tree that the wrapper needs to access)
function App() {
  const [user, setUser] = useState({ name: 'Alice', role: 'admin' })
  return (
    <UserContext.Provider value={{ user, setUser }}>
      <ThemeContext.Provider value="dark">
        <Dashboard />
      </ThemeContext.Provider>
    </UserContext.Provider>
  )
}

// 3. Any child component reads Context
function Dashboard() {
  const { user } = useContext(UserContext)
  const theme = useContext(ThemeContext)
  return <p>{user.name} - Topic:{theme}</p>
}
▶ جرّب الكود

(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) ربط مزود مخصص

JSX
// ============================================
// Example:Custom ThemeProvider + useTheme Hook
// ============================================

const ThemeContext = React.createContext(null)

function ThemeProvider({ children }) {
  const [theme, setTheme] = React.useState(() => {
    // from  localStorage Read the initial topic
    return localStorage.getItem('theme') || 'light'
  })

  function toggleTheme() {
    setTheme(prev => {
      const next = prev === 'light' ? 'dark' : 'light'
      localStorage.setItem('theme', next)  // Persistence
      return next
    })
  }

  const value = React.useMemo(
    () => ({ theme, toggleTheme }),
    [theme]
  )

  return (
    <ThemeContext.Provider value={value}>
      {children}
    </ThemeContext.Provider>
  )
}

// Custom Hook:Packaging useContext + Error Checking
function useTheme() {
  const context = useContext(ThemeContext)
  if (!context) {
    throw new Error('useTheme Must be ThemeProvider For internal use only')
  }
  return context
}

// Usage
function ThemedButton() {
  const { theme, toggleTheme } = useTheme()
  return (
    <button onClick={toggleTheme}
      style={{
        backgroundColor: theme === 'dark' ? '#333' : '#f0f0f0',
        color: theme === 'dark' ? 'white' : '#333',
        padding: '10px 20px',
        border: 'none',
        borderRadius: '4px',
        cursor: 'pointer'
      }}>
      Current Topic:{theme === 'dark' ? '🌙' : '☀️'} Click to toggle
    </button>
  )
}


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، يمكنك إنشاء حل خفيف الوزن لإدارة الحالة العامة:

JSX
// ============================================
// Example:Global Notification System(Context + useReducer)
// ============================================

// ---- 1. Create Context ----
const NotificationContext = createContext(null)

// ---- 2. Definition reducer ----
function notificationReducer(state, action) {
  switch (action.type) {
    case 'ADD':
      return [...state, {
        id: Date.now(),
        message: action.message,
        type: action.notifType || 'info',
        read: false
      }]
    case 'MARK_READ':
      return state.map(n =>
        n.id === action.id ? { ...n, read: true } : n
      )
    case 'REMOVE':
      return state.filter(n => n.id !== action.id)
    case 'CLEAR_ALL':
      return []
    default:
      return state
  }
}

// ---- 3. Provider Components ----
function NotificationProvider({ children }) {
  const [notifications, dispatch] = useReducer(notificationReducer, [])

  // use  useMemo Stable value
  const value = useMemo(() => ({ notifications, dispatch }), [notifications])

  return (
    <NotificationContext.Provider value={value}>
      {children}
    </NotificationContext.Provider>
  )
}

// ---- 4. Custom Hook ----
function useNotification() {
  const context = useContext(NotificationContext)
  if (!context) throw new Error('useNotification Must be NotificationProvider For internal use')
  return context
}

// ---- 5. Using the notification component ----
function NotificationBell() {
  const { notifications, dispatch } = useNotification()
  const unread = notifications.filter(n => !n.read).length

  return (
    <div style={{ position: 'relative', cursor: 'pointer' }}>
      🔔
      {unread > 0 && (
        <span style={{
          position: 'absolute', top: '-8px', right: '-8px',
          backgroundColor: '#ff4d4f', color: 'white',
          borderRadius: '50%', width: '18px', height: '18px',
          fontSize: '12px', display: 'flex', alignItems: 'center',
          justifyContent: 'center'
        }}>
          {unread}
        </span>
      )}
    </div>
  )
}

function NotificationList() {
  const { notifications, dispatch } = useNotification()

  if (notifications.length === 0) {
    return <p style={{ color: '#999', textAlign: 'center' }}>No notice at this time</p>
  }

  return (
    <div style={{ maxWidth: '400px' }}>
      <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '8px' }}>
        <strong>Notice({notifications.length})</strong>
        <button onClick={() => dispatch({ type: 'CLEAR_ALL' })} style={smallBtn}>Clear All</button>
      </div>
      {notifications.map(n => (
        <div key={n.id} style={{
          padding: '8px', marginBottom: '4px',
          backgroundColor: n.read ? '#fafafa' : '#e6f7ff',
          borderRadius: '4px', cursor: 'pointer',
          display: 'flex', justifyContent: 'space-between',
          alignItems: 'center'
        }}
        onClick={() => dispatch({ type: 'MARK_READ', id: n.id })}>
          <span>
            {n.type === 'error' ? '❌' : n.type === 'success' ? '✅' : 'ℹ️'}
            {n.message}
          </span>
          <button onClick={e => { e.stopPropagation(); dispatch({ type: 'REMOVE', id: n.id }) }}
            style={{ ...smallBtn, color: '#ff4d4f' }}>✕</button>
        </div>
      ))}
    </div>
  )
}

// ---- The component that sends notifications ----
function NotificationSender() {
  const { dispatch } = useNotification()
  const [message, setMessage] = useState('')

  function send(type) {
    if (!message.trim()) return
    dispatch({ type: 'ADD', message: message.trim(), notifType: type })
    setMessage('')
  }

  return (
    <div>
      <input value={message} onChange={e => setMessage(e.target.value)}
        placeholder="Notice Content" style={{ width: '100%', padding: '8px', marginBottom: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }} />
      <div style={{ display: 'flex', gap: '4px' }}>
        <button onClick={() => send('info')} style={typeBtn('#1890ff')}>ℹ️ info</button>
        <button onClick={() => send('success')} style={typeBtn('#52c41a')}>✅ Success</button>
        <button onClick={() => send('error')} style={typeBtn('#ff4d4f')}>❌ Error</button>
      </div>
    </div>
  )
}

const smallBtn = { padding: '2px 8px', fontSize: '12px', border: 'none', backgroundColor: 'transparent', cursor: 'pointer' }
const typeBtn = (color) => ({ padding: '6px 12px', backgroundColor: color, color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' })

// ---- 6. Assembly ----
function NotificationApp() {
  return (
    <NotificationProvider>
      <div style={{ maxWidth: '500px', margin: '0 auto' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <h2>🔔 Notification System</h2>
          <NotificationBell />
        </div>
        <NotificationSender />
        <hr style={{ margin: '16px 0' }} />
        <h3>List of Notices</h3>
        <NotificationList />
      </div>
    </NotificationProvider>
  )
}

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


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

JSX
// ============================================
// Example:User Authentication System——Context + Custom Hook Best Practices
// Features:Admin Login/Logged Out,Provide user information globally
// ============================================

import { createContext, useContext, useState, useCallback, useMemo } from 'react'

// ---- 1. Create Context ----
const AuthContext = createContext(null)

// ---- 2. Provider Components ----
function AuthProvider({ children }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(false)

  const login = useCallback(async (username, password) => {
    setLoading(true)
    try {
      // Simulate a login request
      await new Promise(resolve => setTimeout(resolve, 800))
      if (username && password) {
        const loggedInUser = {
          id: 1,
          name: username,
          email: `${username}@example.com`,
          avatar: 'https://i.pravatar.cc/40',
          role: username === 'admin' ? 'Administrator' : 'Regular User'
        }
        setUser(loggedInUser)
        localStorage.setItem('user', JSON.stringify(loggedInUser))
        return { success: true }
      }
      return { success: false, error: 'Incorrect username or password' }
    } finally {
      setLoading(false)
    }
  }, [])

  const logout = useCallback(() => {
    setUser(null)
    localStorage.removeItem('user')
  }, [])

  // use  useMemo Stable value Quote,Avoid Provider Render all child components again
  const value = useMemo(() => ({
    user,
    loading,
    isAuthenticated: !!user,
    login,
    logout
  }), [user, loading, login, logout])

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  )
}

// ---- 3. Custom Hook + Error Checking ----
function useAuth() {
  const context = useContext(AuthContext)
  if (!context) {
    throw new Error('useAuth Must be AuthProvider For internal use only')
  }
  return context
}

// ---- 4. Use Certified Components ----
function LoginForm() {
  const { login, loading } = useAuth()
  const [username, setUsername] = useState('')
  const [password, setPassword] = useState('')
  const [error, setError] = useState('')

  async function handleSubmit(e) {
    e.preventDefault()
    setError('')
    const result = await login(username, password)
    if (!result.success) {
      setError(result.error)
    }
  }

  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: '400px', margin: '0 auto' }}>
      <h2>🔐 Log In</h2>
      {error && <p style={{ color: '#ff4d4f', fontSize: '14px' }}>{error}</p>}
      <input value={username} onChange={e => setUsername(e.target.value)}
        placeholder="Username(Input admin Experience the Role of an Administrator)"
        style={{ width: '100%', padding: '8px', marginBottom: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }} />
      <input type="password" value={password} onChange={e => setPassword(e.target.value)}
        placeholder="Password"
        style={{ width: '100%', padding: '8px', marginBottom: '8px', border: '1px solid #d9d9d9', borderRadius: '4px' }} />
      <button type="submit" disabled={loading}
        style={{ width: '100%', padding: '10px', backgroundColor: '#1890ff', color: 'white', border: 'none', borderRadius: '4px', cursor: loading ? 'not-allowed' : 'pointer' }}>
        {loading ? 'Logging in......' : 'Log In'}
      </button>
    </form>
  )
}

function UserDashboard() {
  const { user, logout } = useAuth()

  return (
    <div style={{ maxWidth: '400px', margin: '0 auto' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '16px' }}>
        <img src={user.avatar} alt={user.name}
          style={{ width: '40px', height: '40px', borderRadius: '50%' }} />
        <div>
          <h3 style={{ margin: 0 }}>{user.name}</h3>
          <p style={{ margin: '4px 0 0 0', color: '#666', fontSize: '13px' }}>
            {user.email} · {user.role}
          </p>
        </div>
      </div>
      <button onClick={logout}
        style={{ padding: '8px 16px', backgroundColor: '#ff4d4f', color: 'white', border: 'none', borderRadius: '4px', cursor: 'pointer' }}>
        Log Out
      </button>
    </div>
  )
}

// ---- 5. Top-level component ----
function AuthApp() {
  const { isAuthenticated } = useAuth()
  return isAuthenticated ? <UserDashboard /> : <LoginForm />
}

// ---- 6. Entrance ----
function App() {
  return (
    <AuthProvider>
      <div style={{ maxWidth: '600px', margin: '0 auto', padding: '20px' }}>
        <AuthApp />
      </div>
    </AuthProvider>
  )
}

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

JSX
const I18nContext = createContext(null)

function I18nProvider({ children }) {
  const [locale, setLocale] = useState('zh')
  const translations = {
    zh: { title: 'App Title', greeting: 'Hello', logout: 'Exit' },
    en: { title: 'App Title', greeting: 'Hello', logout: 'Logout' },
  }
  const t = useCallback((key) => translations[locale][key] || key, [locale])
  const value = useMemo(() => ({ locale, setLocale, t }), [locale, t])
  return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>
}

function useI18n() {
  const ctx = useContext(I18nContext)
  if (!ctx) throw new Error('useI18n must be inside I18nProvider')
  return ctx
}

function LocaleSwitcher() {
  const { locale, setLocale } = useI18n()
  return (
    <select value={locale} onChange={e => setLocale(e.target.value)}
      style={{ padding: '4px 8px', borderRadius: 4 }}>
      <option value="zh">Chinese</option>
      <option value="en">English</option>
    </select>
  )
}

function Greeting() {
  const { t } = useI18n()
  return <h2>{t('greeting')}</h2>
}

function I18nApp() {
  return (
    <I18nProvider>
      <div style={{ maxWidth: 400, margin: '0 auto' }}>
        <div style={{ display: 'flex', justifyContent: 'space-between' }}>
          <Greeting />
          <LocaleSwitcher />
        </div>
      </div>
    </I18nProvider>
  )
}

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

JSX
const UserStateContext = createContext(null)
const UserDispatchContext = createContext(null)

function UserProvider({ children }) {
  const [user, setUser] = useState(null)
  const [loading, setLoading] = useState(false)

  const dispatch = useMemo(() => ({
    login: async (name) => {
      setLoading(true)
      await new Promise(r => setTimeout(r, 500))
      setUser({ name, role: name === 'admin' ? 'Admin' : 'User' })
      setLoading(false)
    },
    logout: () => setUser(null),
  }), [])

  const stateValue = useMemo(() => ({ user, loading }), [user, loading])

  return (
    <UserDispatchContext.Provider value={dispatch}>
      <UserStateContext.Provider value={stateValue}>
        {children}
      </UserStateContext.Provider>
    </UserDispatchContext.Provider>
  )
}

function useUserState() { return useContext(UserStateContext) }
function useUserDispatch() { return useContext(UserDispatchContext) }

function LoginButton() {
  const { login } = useUserDispatch()
  const { loading } = useUserState()
  return <button disabled={loading} onClick={() => login('Alice')}>Login</button>
}

function UserInfo() {
  const { user } = useUserState()
  if (!user) return <p>Not logged in</p>
  return <p>Logged in as {user.name} ({user.role})</p>
}

function SplitContextApp() {
  return (
    <UserProvider>
      <div style={{ maxWidth: 400, margin: '0 auto' }}>
        <UserInfo />
        <LoginButton />
      </div>
    </UserProvider>
  )
}

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

JSX
const SettingsContext = createContext(null)

function SettingsProvider({ children }) {
  const [settings, setSettings] = useState({
    theme: 'light', fontSize: 14, sidebarCollapsed: false,
  })

  const updateSetting = useCallback((key, value) => {
    setSettings(prev => ({ ...prev, [key]: value }))
  }, [])

  const value = useMemo(() => ({ settings, updateSetting }), [settings, updateSetting])

  return <SettingsContext.Provider value={value}>{children}</SettingsContext.Provider>
}

function useSettings() {
  const ctx = useContext(SettingsContext)
  if (!ctx) throw new Error('useSettings must be inside SettingsProvider')
  return ctx
}

function Toolbar() {
  const { settings, updateSetting } = useSettings()
  return (
    <div style={{ padding: 8, background: settings.theme === 'dark' ? '#333' : '#f0f0f0', display: 'flex', gap: 8, alignItems: 'center' }}>
      <button onClick={() => updateSetting('theme', settings.theme === 'dark' ? 'light' : 'dark')}>
        {settings.theme === 'dark' ? '☀️' : '🌙'}
      </button>
      <button onClick={() => updateSetting('sidebarCollapsed', !settings.sidebarCollapsed)}>
        {settings.sidebarCollapsed ? '▶' : '◀'} Sidebar
      </button>
      <select value={settings.fontSize} onChange={e => updateSetting('fontSize', Number(e.target.value))}>
        <option value={12}>12px</option>
        <option value={14}>14px</option>
        <option value={16}>16px</option>
        <option value={18}>18px</option>
      </select>
    </div>
  )
}

function Content() {
  const { settings } = useSettings()
  return (
    <div style={{ fontSize: settings.fontSize, color: settings.theme === 'dark' ? '#eee' : '#333',
      background: settings.theme === 'dark' ? '#1a1a1a' : '#fff', padding: 16 }}>
      <p>Current settings:</p>
      <ul>
        <li>Theme: {settings.theme}</li>
        <li>Font size: {settings.fontSize}px</li>
        <li>Sidebar: {settings.sidebarCollapsed ? 'collapsed' : 'expanded'}</li>
      </ul>
    </div>
  )
}

function SettingsApp() {
  return (
    <SettingsProvider>
      <div style={{ maxWidth: 600, margin: '0 auto' }}>
        <Toolbar />
        <Content />
      </div>
    </SettingsProvider>
  )
}


❓ أسئلة شائعة

س ماذا أفعل إذا كان 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%