
複雑な状態管理に向けて

先生!前回のStateの記事で、useStateを使って状態を管理する方法は分かりました。でも、大きなアプリだともっと複雑な状態管理が必要になるって聞きました!!

いい質問だね、OZくん!そのとおり。useStateは便利だけど、状態が多くなるとコードが煩雑になることがあります!そこで登場するのが、【useContext】と【useReducer】です。

useContextとuseReducer?それってどんなものなんですか?

簡単に言うと、useContextはデータをコンポーネント間で簡単に共有するための仕組みで、useReducerは複雑な状態管理をシンプルにするためのものなんだ。それじゃあ、順番に説明していこう!
1. useContextって何?
グローバルな状態を共有する

useContextを使うと、親から子へpropsで直接データを渡す方法ではなく、コンポーネントのどこからでもデータを受け取れるようになるんです。まず、例を見てみましょう!
import { createContext, useContext } from "react";
const UserContext = createContext();
const ParentComponent = () => {
const user = { name: "OZ", age: 18 };
return (
<UserContext.Provider value={user}>
<ChildComponent />
</UserContext.Provider>
);
};
const ChildComponent = () => {
const user = useContext(UserContext);
return (
<p>
こんにちは!{user.name}くん!年齢は、{user.age}ですね!
</p>
);
};
export default ParentComponent;
コード解説
createContext- グローバルにデータを管理するための「箱」を作成。ここでは
UserContextを作っています。
- グローバルにデータを管理するための「箱」を作成。ここでは
UserContext.Provider- データ(
valueプロパティ)を子コンポーネントに共有するための仕組み。
- データ(
useContext(UserContext)- 子コンポーネントがデータを受け取るときに使う。

これなら、親から子に毎回propsを渡さなくてもいいんですね!
2. 状態が複雑な場合に便利なuseReducer

useReducerは、複雑な状態を管理するときに便利なフックです。useStateと似てるけど、状態の更新ロジックをreducerという関数にまとめて書けるのが特徴です。
カウンターアプリの例
import { useReducer } from "react";
const initialState = { count: 0 };
const reducer = (state, action) => {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
case "reset":
return initialState;
default:
throw new Error("不明なアクションです");
}
};
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
debugger;
return (
<div>
<p>カウント:{state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>増やす</button>
<button onClick={() => dispatch({ type: "decrement" })}>減らす</button>
<button onClick={() => dispatch({ type: "reset" })}>リセット</button>
</div>
);
};
export default Counter;カウンターアプリのコード解説
全体の流れ
- このアプリはカウンターを操作するもので、ボタンを押すと数値が増減したり、リセットされたりします。
- 状態管理に
useReducerを使い、状態(state)を更新するための「アクション」(dispatch)を呼び出します。
const initialState = { count: 0 };これは状態の初期値ですcount: 0として、最初はカウンターが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 initialState;
default:
throw new Error('Unhandled action type: ' + action.type);
}
}- 引数の説明
state:現在の状態(例:{ count: 0 })action:どのように状態を更新するかを指定するオブジェクト(例:{ type: 'increment' })
- switch文の動き
action.typeがincrementの場合、countを1増やした新しい状態を返しますdecrementの場合、countを1減らしますresetの場合、initialStateに戻します- 想定外の
action.typeが渡された場合はエラーを投げます(バグ防止)
const [state, dispatch] = useReducer(reducer, initialState);useReducerは2つの引数を受け取ります。
- 状態を更新するための
reducer関数 - 状態の初期値(
initialState)
戻り値として、次の2つが返されます。
state:現在の状態(例:{ count: 0 })dispatch:状態を更新するための関数
(例:dispatch({ type: 'increment' }))
return (
<div>
<p>カウント: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>増やす</button>
<button onClick={() => dispatch({ type: 'decrement' })}>減らす</button>
<button onClick={() => dispatch({ type: 'reset' })}>リセット</button>
</div>
);state.count:現在のカウント値を表示。各ボタン:onClickイベントでdispatchを呼び出し、状態を更新します。
incrementボタン:カウントを1増やすdecrementボタン:カウントを1減らすresetボタン:初期状態にリセットする
3. useContextとuseReducerを組み合わせる

useContextとuseReducerを組み合わせると、もっと便利になりますよ。例えば、大規模アプリで複数のコンポーネント間で状態を共有しながら管理する場合を考えてみましょう!!
Todoリストの例
import React, { createContext, useReducer, useContext } from 'react';
// 初期状態とreducer関数
const initialState = [];
function reducer(state, action) {
switch (action.type) {
case 'add':
return [...state, action.payload];
case 'remove':
return state.filter((_, index) => index !== action.payload);
default:
throw new Error('Unhandled action type: ' + action.type);
}
}
// Contextの作成
const TodoContext = createContext();
function TodoProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<TodoContext.Provider value={{ state, dispatch }}>
{children}
</TodoContext.Provider>
);
}
function TodoList() {
const { state, dispatch } = useContext(TodoContext);
return (
<div>
<ul>
{state.map((todo, index) => (
<li key={index}>
{todo} <button onClick={() => dispatch({ type: 'remove', payload: index })}>削除</button>
</li>
))}
</ul>
<button
onClick={() => dispatch({ type: 'add', payload: `新しいタスク ${state.length + 1}` })}
>
タスクを追加
</button>
</div>
);
}
function App() {
return (
<TodoProvider>
<TodoList />
</TodoProvider>
);
}
export default App;
Todoリストのコード解説
全体の流れ
- このアプリはタスク(Todo)を管理するもので、新しいタスクを追加したり、削除したりできます。
- 複数のコンポーネントで状態を共有するため、
useReducerとuseContextを組み合わせています。
const initialState = [];タスクが最初は何もない状態を配列[]で定義しています。
function reducer(state, action) {
switch (action.type) {
case 'add':
return [...state, action.payload];
case 'remove':
return state.filter((_, index) => index !== action.payload);
default:
throw new Error('Unhandled action type: ' + action.type);
}
}動作の説明
addアクションの場合:action.payload(新しいタスク)を現在の状態(配列)に追加します。[...state, action.payload]はスプレッド構文を使って新しい配列を作成します。
removeアクションの場合:- 指定されたインデックス(
action.payload)を除く配列を返します。 filterメソッドで不要なタスクを除外します。
- 指定されたインデックス(
const TodoContext = createContext();TodoContextを作成し、アプリ全体で状態を共有できる仕組みを用意します。
function TodoProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<TodoContext.Provider value={{ state, dispatch }}>
{children}
</TodoContext.Provider>
);
}useReducerで管理している状態とdispatchをコンテキストに渡します。
子コンポーネントでTodoContextを使えば、これらの値を利用できます。
function TodoList() {
const { state, dispatch } = useContext(TodoContext);
return (
<div>
<ul>
{state.map((todo, index) => (
<li key={index}>
{todo} <button onClick={() => dispatch({ type: 'remove', payload: index })}>削除</button>
</li>
))}
</ul>
<button
onClick={() => dispatch({ type: 'add', payload: `新しいタスク ${state.length + 1}` })}
>
タスクを追加
</button>
</div>
);
}useContextの利用
TodoContextからstateとdispatchを取得します。
タスクの表示
state.mapで現在のタスクをループし、それぞれをリストアイテム(<li>)として表示します。- 各アイテムには削除ボタンがあり、クリックすると
removeアクションが呼び出されます。
タスクの追加
- 追加ボタンを押すと、
dispatchでaddアクションを呼び出し、新しいタスクを配列に追加します。

なるほど!こうすれば、どのコンポーネントからでもTodoリストを操作できますね!
次回に向けて

先生、今日もたくさん学べました!次はReduxの話を教えてください!

もちろん!Reduxは、さらに状態管理を強力にしてくれるライブラリです。次回を楽しみにしていてくださいね!


