こんにちは!Reactの学習、毎日お疲れ様です。
画面に文字を入力したらちゃんと動くし、ボタンを押したらカウントが増える……。「おっ、Reactって結構楽しいぞ!」と感じてきた頃だと思います。でも、アプリの規模が少し大きくなってくると、こんなモヤモヤにぶつかりませんか?
- 「あっちのコンポーネントにあるデータを、こっちのコンポーネントでも使いたいだけなのに、props(プロップス)のリレーが大変すぎる……!」
- 「どこでデータを書き換えたのか分からなくなって、バグの犯人探しで一日が終わっちゃった……」
外部のすごいライブラリ(ReduxとかZustandとか)を入れる手もありますが、実はReact標準の機能だけでも、この「あっちこっちでデータが迷子になる問題」はキレイに解決できちゃうんです。
今回は、React標準の `useReducer` と `useContext` という2つの強力なフーズ(機能)を組み合わせて、外部ライブラリなしでスッキリとしたグローバル状態管理を作る方法を、一緒に優しく紐解いていきましょう!
大丈夫です、一歩ずつ見ていけば絶対に理解できますからね。
—
1. 例え話:みんなのお財布はどこにある?
まずはイメージから入りましょう。
家族みんなでお出かけしているところを想像してください。お父さん、お母さん、子どもたちがそれぞれお小遣いを持って買い物をしています。
- 「あれ買いたい!」
- 「ちょっとジュース代ちょうだい!」
こんなとき、家族全員がそれぞれ自分のポケットからお金をバラバラに出していたら、家計がどうなっているか分からなくなりますよね。
そこで、「家族みんなで共有するお財布(金庫)」を一つだけリビングにドンと置くことにしました。
1. お金を使うときは、必ずそのお財布の管理人に「これだけ使っていい?」とお願いする。
2. 管理人が「オッケー、じゃあこういう理由で残高を減らすね」と、ルール通りにしっかり計算してお金を出す。
3. 家族みんなが、そのお財布の中身をいつでもどこでも見られるようにする。
Reactの世界もこれとまったく同じです。
この「みんなのお財布(金庫)」を作るのが `useContext` で、「お財布の管理ルール(お金の出し入れ係)」を決めるのが `useReducer` なんです。
—
2. 登場人物のおさらい
少しだけ、それぞれの機能の役割を整理しておきますね。
- `useContext`(コンテキスト)
- 役割: 「データの通り道」を一瞬でつくる魔法。
- なぜ使うの?: 親から子、孫、ひ孫……と、何段階も `props` をバケツリレーしなくても、必要なコンポーネントが「ちょちょいのちょい」と直接データを取りに行けるようになります。
- `useReducer`(リデューサー)
- 役割: 「状態の更新ルール」をきっちり整理する番人。
- なぜ使うの?: 「`useState` だと、あちこちで勝手にデータを書き換えちゃってカオスになった……」という状態を防ぎます。「こういう命令が来たら、こうデータを変化させる」というレシピ(関数)を一つにまとめて安全に管理します。
この二つをガッチリ組ませることで、「どこからでもデータを安全にいじれる、最強の共有スペース」が完成します。
—
3. 実践!お買い物アプリを作ってみよう
百聞は一見に如かず。実際に動くコードを見てみましょう!
今回は、ボタンを押すと「カートの中身の合計金額が変わる」ような、小さなお買い物アプリを想定して作ります。
このコードをそのままコピペして、あなたの開発環境(CodeSandboxやViteなど)で動かしてみてくださいね。
import React, { createContext, useContext, useReducer } from ‘react’;
// 1. 【Contextの作成】
// アプリ全体で共有する「お財布(金庫)」の置き場所を作ります。
const ShoppingContext = createContext();
// 2. 【Reducerの作成】
// 「こういう注文(アクション)が来たら、状態をこう変えますよ」というルールブックです。
// お金の計算間違いが起きないように、ここで一括管理します。
const initialState = {
cartCount: 0,
totalPrice: 0,
};
function shoppingReducer(state, action) {
// action.type(どんな命令か)によって処理を分岐します
switch (action.type) {
case ‘ADD_ITEM’:
return {
…state, // いままでの状態を一度コピーして
cartCount: state.cartCount + 1, // カウントを1増やす
totalPrice: state.totalPrice + action.payload, // 商品の値段を加算する
};
case ‘RESET_CART’:
return initialState; // 初期状態に戻す
default:
// 想定外の命令が来たときのエラー対策(これがあるとデバッグが楽になります)
throw new Error(`未知のアクションです: ${action.type}`);
}
}
// 3. 【Provider(プロバイダー)コンポーネントの作成】
// お財布と管理ルールを包み込んで、子どもたちに配る「親御さん」的な役割です。
export function ShoppingProvider({ children }) {
// useReducerに「ルール(shoppingReducer)」と「初期値(initialState)」を渡します
// state: 現在のデータ, dispatch: ルールに従って命令を送るための道具
const [state, dispatch] = useReducer(shoppingReducer, initialState);
return (
// valueの中に、渡したいデータ(state)と命令(dispatch)を詰め込みます
{children}
);
}
// 4. 【カスタムフックの作成】
// Contextを使うときのおまじないを簡単にするためのラッパーです。
function useShopping() {
return useContext(ShoppingContext);
}
// ==========================================
// ここから下は、実際に画面に表示するパーツたちです
// ==========================================
// ヘッダーパーツ(現在のカート状況を表示)
function Header() {
// お財布から現在の状態を取り出します
const { state } = useShopping();
return (
🛒 マイショップ
カートの中身: {state.cartCount} 個
合計金額: {state.totalPrice} 円
);
}
// 商品カードパーツ(ボタンを押すとカートに追加される)
function ProductCard({ name, price }) {
// お財布から「命令を送る道具(dispatch)」を取り出します
const { dispatch } = useShopping();
const handleAddToCart = () => {
// dispatchを使って、「ADD_ITEMという命令を、値段と一緒に送るよ!」と伝えます
dispatch({ type: ‘ADD_ITEM’, payload: price });
};
return (
{name}
{price}円
);
}
// カートをリセットするボタンのパーツ
function ResetButton() {
const { dispatch } = useShopping();
return (
);
}
// メインのAppコンポーネント
// ShoppingProviderで全体を包み込むことで、どこからでもデータにアクセスできるようになります
export default function App() {
return (
);
}
—
4. チーフアーキテクトからのワンポイントアドバイス
ここまで読んでくれてありがとうございます!
最後に、実務の現場でこのパターンを使うときの「ちょっとしたコツ」や「注意点」をお伝えしておきますね。
① `dispatch` は変わらないという安心感
Reactの公式ドキュメントにも書いてありますが、`useReducer` が返す `dispatch` 関数のアイデンティティ(参照)は、アプリが起動している間ずっと変わりません。そのため、`useEffect` の依存配列に `dispatch` を入れても、余計な再レンダリングが起きる心配がありません。これはパフォーマンス面において地味にめちゃくちゃ嬉しいポイントです。
② すべてをContextに入れすぎない!
「よし、便利だからアプリのすべての状態を1つの巨大なContextに入れちゃえ!」……これは初心者が陥りがちなしっかりとした罠です。
Contextの値が更新されると、そのContextを購読している(`useContext`を使っている)すべてのコンポーネントが再レンダリングされます。
例えば、入力フォームの「文字一文字の入力」レベルの状態をグローバルContextに入れてしまうと、関係ない画面の端っこまで再レンダリングされて動作がカクつく原因になります。
- グローバルにすべきもの: ログイン中のユーザー情報、テーマ設定(ダークモードなど)、ショッピングカートの状態など、アプリ全体であちこちから参照されるもの。
- ローカルでいいもの: モーダルが開いているかどうか、入力フォームの一時的な文字、アコーディオンの開閉など。
適材適所で `useState` と使い分けるのが、一流のReactエンジニアへの近道です!
—
おわりに
いかがでしたでしょうか?
`useReducer` と `useContext` の組み合わせは、一見するとコードの行数も増えるし難しそうに見えます。でも、「データの置き場所を決める人」と「データのルールを決める人」をしっかり分けることで、アプリが成長しても「バグを探しやすい、見通しの良いコード」を保つことができます。
最初は魔法のように感じるかもしれませんが、自分で手を動かして「あ、こうやってデータが流れてるんだな」と体感できれば、もう怖くありません。
もし途中でコードが動かなくなったり、「ここはどういう意味?」と迷ったら、いつでもこの記事に戻ってきてくださいね。あなたのReact学習を、心から応援しています!

コメント