React · LEARN AT YOUR OWN PACE今日も、一歩ずつ。

LESSON 10 · 約40分

Contextとreducerで、状態を整理する

深い受け渡しと複雑な更新を、別々の道具で解決します。

コードを試す前に

01のVite reactテンプレート(JavaScript/JSX)を使います。特記のないApp.jsxは、そのレッスンだけで動く独立した例です。例同士を連結しないでください。

短い学習用の例です。各App.jsxは独立した例で、順に追記して一つの完成品にする形式ではありません。ファイル名、依存関係、未定義部分の前提は注記に記載します。問題内の抜粋や空欄は、そのまま実行するための完成コードではありません。

開発環境の準備を確認する →

このレッスンでわかること

  • Contextとreducerが解決する問題を区別できる
  • 純粋なreducerへactionを渡せる

Contextは値の届け方を変える

共通の設定を深い階層へ渡す場合、Contextが途中のpropsの受け渡しを減らします。createContextで箱を定義し、上位のProviderが値を渡し、下位がuseContextで読みます。最も近いProviderが使われ、なければ既定値になります。Contextだけで保存やstate更新の仕組みが増えるわけではありません。

reducerは更新の判断をまとめる

追加・削除・切り替えなどが増えたら、現在のstateとactionから次のstateを返すreducerに整理できます。イベントはdispatch({ type: ... })で何が起きたか伝えます。reducerは通信せず、元の状態を変更せず、次の状態を返す純粋な関数にします。

小さいうちは単純に始める

近い親子ならprops、単純な状態ならuseStateで十分です。Contextとreducerは一緒にも別々にも使えます。この例はテーマをContextで届け、カウント更新だけをreducerにまとめます。すべての状態をアプリ最上位へ集めることが目的ではありません。

テーマの共有と、カウントの更新

src/App.jsx
import { createContext, useContext, useReducer, useState } from 'react';

const ThemeContext = createContext('light');

function counterReducer(state, action) {
  switch (action.type) {
    case 'increase': return { count: state.count + 1 };
    case 'reset': return { count: 0 };
    default: throw new Error(`Unknown action: ${action.type}`);
  }
}

function Counter() {
  const theme = useContext(ThemeContext);
  const [state, dispatch] = useReducer(counterReducer, { count: 0 });
  return (
    <section style={{ color: theme === 'dark' ? '#fff' : '#111',
      background: theme === 'dark' ? '#222' : '#fff', padding: 16 }}>
      <h2>練習カウンター</h2>
      <p aria-live="polite">{state.count}回</p>
      <button type="button" onClick={() => dispatch({ type: 'increase' })}>増やす</button>
      <button type="button" onClick={() => dispatch({ type: 'reset' })}>リセット</button>
    </section>
  );
}

function Content() { return <Counter />; }

export default function App() {
  const [theme, setTheme] = useState('light');
  return (
    <ThemeContext.Provider value={theme}>
      <h1>状態を整理する</h1>
      <button type="button" onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
        テーマを切り替える
      </button>
      <Content />
    </ThemeContext.Provider>
  );
}

Provider構文はReact 19でも利用できます。React 19では<ThemeContext value={theme}>とも書けます。この例は両者を混在させずProviderに統一しています。

TRY IT YOURSELF

テーマ共有と更新を分ける

  1. テーマ変更がCounterへ届き、Contentにtheme propsがないことを確認する
  2. decrease actionを追加し、0未満にしない更新を書く
  3. state={count: 2}とincrease actionを渡した場合の結果を紙で求める
  4. reducerに保存処理を書かず、純粋な状態計算だけに保つ

できたらOK:テーマ共有とカウント更新を独立して説明でき、どちらの変更でも他方の値が壊れない

QUICK CHECK

理解を確かめよう

深い階層の部品が同じテーマ設定を必要としている。Contextについて正しい判断はどれ?

公式資料でもう少し詳しく

ここまで読めたら、ひとつ前進。