🎯 はじめに
Reactの主要なHooks(useState, useEffect, useContext, useReducer)を一通り学んだあと、
「実際にどう組み合わせて使うの?」と思う方も多いのではないでしょうか。
この記事では、これら4つを組み合わせて
「ダークモード/ライトモード切り替えアプリ」 を作成します。
UIの変化が目で見てわかるので、復習にも最適です!
🧩 前提条件
React(ViteやCRAなど)環境が整っていること
基本的なHooksの使い方を理解していること
CSSは最小限でOK(Tailwindなどは使いません)
🚀 今回の目標
| フック | 役割 |
|---|---|
| useState | 現在のテーマ(light/dark)を保持 |
| useEffect | localStorageとの同期処理 |
| useContext | テーマをアプリ全体で共有 |
| useReducer | 状態更新ロジックを整理・管理 |
🏗️ アプリ構成
src/
├─ App.jsx
├─ ThemeContext.jsx
├─ components/
│ └─ ThemeToggler.jsx
└─ index.css
🧱 1. ContextとReducerの作成(ThemeContext.jsx)
// src/ThemeContext.jsx
import React, { createContext, useReducer, useEffect } from "react";
// Context作成
export const ThemeContext = createContext();
const initialState = {
theme: "light", // デフォルトはlight
};
// Reducer
function themeReducer(state, action) {
switch (action.type) {
case "TOGGLE_THEME":
return {
theme: state.theme === "light" ? "dark" : "light",
};
default:
return state;
}
}
// Providerコンポーネント
export const ThemeProvider = ({ children }) => {
const [state, dispatch] = useReducer(themeReducer, initialState);
// Effect localStorageとの同期
useEffect(() => {
const savedTheme = localStorage.getItem("theme");
if (savedTheme) {
dispatch({ type: "SET_THEME", payload: savedTheme });
}
}, []);
useEffect(() => {
localStorage.setItem("theme", state.theme);
document.body.className = state.theme; // bodyにクラス付与
}, [state.theme]);
return (
<ThemeContext.Provider value={{ state, dispatch }}>
{children}
</ThemeContext.Provider>
);
};
💡 2. メインアプリ(App.jsx)
// src/App.jsx
import React from "react";
import { ThemeProvider } from "./ThemeContext";
import ThemeToggler from "./components/ThemeToggler";
import "./index.css";
function App() {
return (
<ThemeProvider>
<div className="app-container">
<h1>React Hooks 総復習</h1>
<p>useState・useEffect・useContext・useReducerを使ってテーマを切り替え!</p>
<ThemeToggler />
</div>
</ThemeProvider>
);
}
export default App;
🔘 3. ボタンコンポーネント(ThemeToggler.jsx)
// src/components/ThemeToggler.jsx
import React, { useContext } from "react";
import { ThemeContext } from "../ThemeContext";
function ThemeToggler() {
const { state, dispatch } = useContext(ThemeContext);
return (
<div>
<button
onClick={() => dispatch({ type: "TOGGLE_THEME" })}
className="theme-button"
style={{ color: state.theme === "light" ? "black" : "white" }}
>
現在のテーマ: {state.theme}(クリックで切替)
</button>
</div>
);
}
export default ThemeToggler;
🎨 4. 最小限のCSS(index.css)
/* src/index.css */
body {
margin: 0;
font-family: sans-serif;
transition: background-color 0.3s, color 0.3s;
}
body.light {
background-color: #ffffff;
color: #222;
}
body.dark {
background-color: #222;
color: #f5f5f5;
}
.app-container {
text-align: center;
padding: 40px;
}
.theme-button {
padding: 10px 20px;
cursor: pointer;
border: 1px solid #888;
background: none;
font-size: 16px;
}
🧠 フックの関係図(概念的な図)
useReducer ──▶ state管理(theme: light/dark)
│
▼
useContext ──▶ 子コンポーネントへ状態とdispatchを共有
│
▼
useEffect ──▶ localStorageとDOM(body.className)を同期
│
▼
useState ──▶ 内部的にはuseReducerの中で使われている形
🔍 ポイント解説
useReducer × useContext の組み合わせで「グローバル状態管理」が可能
useEffect は「副作用(localStorage, DOM操作)」に使う
useState を直接使わず、useReducerで一元管理することで拡張性UP
🏁 まとめ
Hooksは「単体で学ぶ → 組み合わせて使う」と理解が深まる
useReducerとuseContextの組み合わせは、Reduxのような構造を再現できる
useEffectで永続化を実現すると、アプリがぐっと実用的に!
🎬 最後に
ここまで理解できたら、次は「useMemo」と「useCallback」を一緒に学びましょう。
最後までご覧いただきありがとうございます。これからも勉強を兼ねて記事を投稿していきますので、温かく見守ってください。もし間違いなどございましたら、教えて頂けると幸いです。