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にまとめます。すべての状態をアプリ最上位へ集めることが目的ではありません。
テーマの共有と、カウントの更新
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
テーマ共有と更新を分ける
- テーマ変更がCounterへ届き、Contentにtheme propsがないことを確認する
- decrease actionを追加し、0未満にしない更新を書く
- state={count: 2}とincrease actionを渡した場合の結果を紙で求める
- reducerに保存処理を書かず、純粋な状態計算だけに保つ
できたらOK:テーマ共有とカウント更新を独立して説明でき、どちらの変更でも他方の値が壊れない
QUICK CHECK
理解を確かめよう
公式資料でもう少し詳しく
ここまで読めたら、ひとつ前進。