404 Not Found

404 Not Found


nginx

コンテキストとグローバル状態

1. 学習内容



2. テーマの変更に関する話

(1) 課題:5階層のコンポーネントを通過するプロップ

アリスは、アプリに「ダークモード」の切り替え機能を追加したいと考えています。データフローは、5層のコンポーネントを経由する必要があります:

(1) ▶ サンプル:コンテキストの使用

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 を使用)
ユーザーログイン情報 フォームの入力値(ローカル状態を使用)
言語・国際化 APIデータ(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 コンシューマーのラップ プロパティに変更がない場合の再レンダリングのスキップ コンシューマーコンポーネントが他のプロパティを受け取った場合
選択的サブスクリプション セレクタを使用して、必要なスライスのみを取得 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. コンテキストと 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>
  )
}

期待される出力:完全な通知システム。上部には(未読通知数を表示する)通知ベルが表示され、中央ではユーザーが「情報」「成功」「エラー」の3種類の通知を送信でき、下部には通知の一覧が表示され、各通知を「既読」にマークしたり削除したりすることができます。


(1) ▶ サンプル:ユーザー認証ステータスの管理(コンテキスト + カスタムフック)

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>
  )
}
▶ 試してみよう

(2) ▶ サンプル: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>
  )
}
▶ 試してみよう

(3) ▶ サンプル:パフォーマンスを最適化するためのコンテキストの分割

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>
  )
}
▶ 試してみよう

(4) ▶ サンプル:プロップドリリングの代替として useContext を使用する完全な例

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>
  )
}
▶ 試してみよう

❓ よくある質問

Q Contextvalueが毎回新しいオブジェクトになり、その結果すべてのConsumerが再レンダリングされてしまう場合はどうすればよいですか?
A useMemoを使用して、valueへの参照を固定してください:const value = useMemo(() => ({ theme, toggleTheme }), [theme])。また、「安定した」データと「変化する」データを別々の Context に分けることもできます。最適化後もパフォーマンスの問題が解消されない場合は、Zustand の使用を検討してください(Zustand は精密な更新をサポートしており、無関係なコンポーネントの再レンダリングを引き起こしません)。
Q コンテキストのネスト階層が多すぎる場合はどうすればよいですか?
A ① プロバイダー(前述の AppProviders など)を使用してコンポーネントをグループ化し、ネスト構造をカプセル化します。② 本当にそれほど多くのコンテキストが必要かどうかを検討してください。一部の「グローバル」データは、実際には単に「ローカルなグローバル」データ(特定のページの設定など)に過ぎず、アプリの最上位コンテキストに配置する必要はありません。
Q カスタム useXxx フック内で、context が null かどうかを確認するのはなぜですか?
A これは防御的プログラミングの手法です。開発者が useContext を使用する前にコンポーネントを Provider でラップするのを忘れた場合、context は null になるか(createContext にデフォルト値が渡されていない場合)、デバッグが困難なエラーを引き起こします。早い段階で明確なエラーメッセージをスローする方が、ユーザーがブラウザのコンソールで「Cannot read property of null」というメッセージを目にするよりも、はるかにユーザーフレンドリーです。
Q ContextはReduxの代わりになりますか?
A 中小規模のプロジェクトであれば可能ですが、大規模なプロジェクトでは不可能です。Contextには、Reduxが持つ以下の3つの機能が欠けています:① ミドルウェアの仕組み(ロギング、非同期処理、永続化);② タイムトラベルデバッグ(DevToolsでの状態変化の再生); ③ 選択的サブスクリプション(Contextの値が変更されるとすべてのコンシューマーが再レンダリングされますが、Reduxではセレクターを使用して必要な部分のみをサブスクライブできます)。プロジェクトでこれら3つの機能が必要な場合は、Zustand(軽量)またはRedux Toolkitを使用してください。

📖 まとめ


📝 練習問題

  1. 基本問題(難易度 ⭐):中国語と英語の切り替えを可能にする locale (zh/en) および toggleLocale() メソッドを提供する LocaleProvider + useLocale フックを作成してください。
  2. 上級演習(難易度 ⭐⭐):ユーザーの認証ステータスを管理するための userlogin(username, password)、および logout() メソッドを提供する AuthProvider + useAuth フックを作成してください。
  3. 課題(難易度:⭐⭐⭐)CartProvideruseCart を組み合わせたフックを作成し、Context と useReducer を使用して、商品の追加、削除、数量の変更、カートのクリアを行う機能を備えたショッピングカートを実装してください。useMemo を使用してコードを最適化し、不要な再レンダリングを回避してください。
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%