🧭 はじめに
前回の記事では、useContext を使ったグローバルな状態共有について紹介しました。
しかし、アプリが大きくなるにつれて「状態更新のロジックが複雑になってきた…」と感じることはありませんか?
そんなときに活躍するのが useReducer です。
今回は、useContext とうまく連携しながら、よりわかりやすく状態を管理する方法を学びましょう。
🧩 前提条件
-
Reactの基本(useState, useEffect, useContext)を理解している
-
環境:Vite または Create React App
-
JavaScriptベース(TypeScriptでも応用可)
🎯 今回の目標
useReducer の基本的な使い方を理解する
useContext と組み合わせてグローバル状態を管理できるようになる
⚙️ useReducerとは?
useReducer は、複雑な状態管理を整理するための useStateの上位互換 のようなフックです。
状態の更新処理を「reducer関数」として1か所にまとめられるため、ロジックが明確になります。
const [state, dispatch] = useReducer(reducer, initialState)
state:現在の状態
dispatch:状態を更新するための関数
reducer:状態をどう変えるかを定義した関数
🧠 基本構文サンプル
カウンターを例にしてみましょう。
import React, { useReducer } from "react";
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
case "RESET":
return { count: 0 };
default:
return state;
}
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div className="p-4 text-center">
<h2>Count: {state.count}</h2>
<div className="space-x-2 mt-2">
<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
<button onClick={() => dispatch({ type: "RESET" })}>リセット</button>
</div>
</div>
);
}
✅ dispatch に「 どんな操作をしたいか(type) 」を送るだけで状態を更新できます。
✅ reducer 関数でロジックを一元管理できるため、コードがすっきりします。
🌐 useContextと組み合わせる
複数コンポーネントで同じ状態を使いたい場合、
useReducer と useContext を組み合わせることで「軽量なRedux」のように使えます。
1️⃣ Contextを作成
import React, { createContext, useReducer, useContext } from "react";
const CounterContext = createContext();
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
default:
return state;
}
}
export function CounterProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<CounterContext.Provider value={{ state, dispatch }}>
{children}
</CounterContext.Provider>
);
}
// カスタムフックでContextを使いやすくする
export const useCounter = () => useContext(CounterContext);
2️⃣ 子コンポーネントで使う
import React from "react";
import { useCounter } from "./CounterContext";
export function CounterDisplay() {
const { state } = useCounter();
return <h2>Count: {state.count}</h2>;
}
export function CounterButtons() {
const { dispatch } = useCounter();
return (
<div className="space-x-2">
<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
</div>
);
}
3️⃣ Appでまとめる
import React from "react";
import { CounterProvider } from "./CounterContext";
import { CounterDisplay, CounterButtons } from "./CounterComponents";
export default function App() {
return (
<CounterProvider>
<div className="p-4 text-center">
<h1 className="text-xl font-bold mb-2">useReducer + useContext例</h1>
<CounterDisplay />
<CounterButtons />
</div>
</CounterProvider>
);
}
✅ useReducer が状態管理ロジックを担当
✅ useContext がその状態を全コンポーネントで共有
👉 結果、シンプルでスケーラブルな構成になります!
💡 useStateとの違いまとめ
| 比較項目 | useState | useReducer |
|---|---|---|
| 状態の更新 | 直接set関数で行う | dispatch経由でactionを送る |
| 更新ロジック | 分散しやすい | reducerに一元化できる |
| スケーラビリティ | 小規模向け | 中〜大規模向け |
🚀 まとめ
-
useReducer は複雑な状態管理をシンプルに整理できる
-
useContext と組み合わせるとグローバル管理も簡単
-
Reduxより軽量で、React標準機能だけで完結できる
🏁 最後に
React Hooksを使いこなすポイントは、「目的に応じてフックを使い分けること」です。
useState で十分な場面もあれば、アプリが成長して useReducer + useContext の方が見通しが良くなる場合もあります。
小さなサンプルを通して、下記を意識して理解を深めましょう。
- reducerのロジックをどこまで切り出すか
- Contextをどこでラップするか
最後までご覧いただきありがとうございます。今後も引き続き復習を兼ねて投稿していきたいと思います。
他にも記事を投稿しておりますので、是非そちらもご覧ください。今後もよろしくお願いいたします。
🔖 関連記事