【標準機能でここまでやる】React Context × useReducer で作る「軽量・最強」のグローバル状態管理
86日目に useReducer で状態更新のルールを整理しました。本日87日目は、その知識をさらに広げ、アプリ全体で状態を共有する「Context API × useReducer(グローバル状態管理)」 を学習します。
外部ライブラリ(ReduxやZustand)に頼らなくても、標準機能だけで「大規模な状態管理」を作るプロのレシピを伝授します!
はじめに:Prop Drilling(バケツリレー)からの解放
コンポーネントツリーの深い階層にあるボタンのために、親から子、孫へと延々と props を渡す「バケツリレー」に疲れていませんか?
Context API を使えば、ツリーの頂点から「状態(State)」と「更新関数(Dispatch)」を直接地下深くへワープさせることができます。
💡 1. 組み合わせの魔法
Context だけだと「Stateの更新ロジック」がバラバラになりがちですが、useReducer を組み合わせることで、「状態の保持(Context)」と「更新ルールの集約(Reducer)」 という最強のタッグが組めます。
💻 実装例:グローバルな TodoProvider の作成
// 1. Contextの作成
const TodoContext = createContext(null);
// 2. Providerでstateとdispatchを共有
export const TodoProvider = ({ children }) => {
const [state, dispatch] = useReducer(todoReducer, initialState);
return (
<TodoContext.Provider value={{ state, dispatch }}>
{children}
</TodoContext.Provider>
);
};
💡 2. 使うときは「フック」を自作する
毎回 useContext(TodoContext) と書くのは面倒ですよね。専用のカスタムフックを用意するのが通なやり方です。
// 💡 コンポーネント側で簡単に使えるフック
export const useTodo = () => {
const context = useContext(TodoContext);
if (!context) throw new Error('useTodoはProviderの中で使ってください');
return context;
};
// 💡 孫コンポーネントで使うだけ!
const TaskList = () => {
const { state, dispatch } = useTodo();
return <button onClick={() => dispatch({ type: 'DELETE', id: 1 })}>削除</button>;
};
💡 3. いつ使うべきか?(見極めポイント)
このパターンは非常に強力ですが、すべてのアプリで使う必要はありません。
向いている: 認証状態(ログインユーザー)、テーマ設定(ダークモード)、複雑なフォーム状態。
避けるべき: 頻繁に更新されるステータス(タイマーの秒数など)。Contextは更新されると、そのContextを使っているすべてのコンポーネントを再レンダリングしてしまうからです。
📝 87日目のまとめと課題
✅ 本日の習得ポイント
Providerパターン: アプリのルートで状態を包み込む「ストア」の作り方。
カスタムフックの抽象化: コンポーネントを Context の存在から隔離する(useTodo のように呼び出せるようにする)。
パフォーマンスへの配慮: 頻繁に更新されるものには使わないという「引き算」の設計。
📝 87日目の課題
これまでに作ったアプリで、複数の場所から「同じ状態(例:ログイン中のユーザー情報やToDoリスト)」を参照している箇所を探しましょう。
その状態を Context に移動させ、propsの受け渡しを減らしてスッキリさせてみてください。
