React Hooks の useReducer ってどんな機能があるの?
今回は、React Hooks の中でも重要な useReducer について、基本的な考え方から使い方、代表的なユースケースまでをわかりやすく解説します。
useReducer とは?
useReducer とは、複数の状態を持つコンポーネント において、複雑なstateの更新処理を reducer関数に集約し、コンポーネントの外に分離して管理するための Hooks です。
// const [現在の状態, dispatch 関数(更新内容を伝える)] = useReducer(reducer 関数(更新内容に従って新しい state を返す), 初期値);
const [state, dispatch] = useReducer(reducer, initialState);
※ useReducer は dispatch を通して state の更新内容を reducer に伝え、reducer は受け取った内容をもとに、新しい state を返す。
コード例
カウント
処理フロー
- “+2” ボタンを クリック
- countUp = () => dispatch({ type: ‘up’, step: 2 }) が呼ばれる
- reducer の case ‘up’ が実行され、{count: state.count + action.step } が return される
- カウント: が “+2” ずつ カウントアップ される
- “-1” ボタンを クリック
- countDown = () => dispatch({ type: ‘down’, step: -1 }) が呼ばれる
- reducer の case ‘down’ が実行され、{count: state.count + action.step } が return される
- カウント: が “-1” ずつ カウントダウン される
- “stay” ボタンをクリック
- countStay = () => dispatch({ type: ‘stay’ }); が呼ばれる
- reducer の ‘stay‘ が case に該当しないため、default が実行され、今の state がそのまま return される
- カウント: の 値は変わらない
- “リセット” ボタンをクリック
- countReset = () => dispatch({ type: ‘reset’, init: initialState }); が呼ばれる
- reducer の case ‘reset’ が実行され、action.init が return される
- カウント: が 初期値の 0 に戻される
import { useReducer } from 'react';
// 初期 state
const initialState = { count: 0 };
// state の更新ルールを書く関数
function reducer(state, action) {
switch (action.type) {
case 'up':
case 'down':
// count を +2 or -1 する
return { count: state.count + action.step };
case 'reset':
// 初期 state の count: 0 に戻す
return action.init;
default:
// 該当しない場合(stay)は、今の state をそのまま返す
return state;
}
}
export default function UseReducerCounter() {
// state:現在の状態
// dispatch:更新内容を reducer に伝える関数
// reducer: dispatch の更新内容に従って新しい state を返す
// initialState: 初期値
const [state, dispatch] = useReducer(reducer, initialState);
// countUp +2 ボタンの処理
const countUp = () => dispatch({ type: 'up', step: 2 });
// countDown -1 ボタンの処理
const countDown = () => dispatch({ type: 'down', step: -1 });
// stay ボタンの処理
const countStay = () => dispatch({ type: 'stay' });
// reset ボタンの処理
const countReset = () => dispatch({ type: 'reset', init: initialState });
return (
<div>
<h2>UseReducer Counter</h2>
<p>カウント: {state.count}</p>
{/* count を +2 したいことを伝える */}
<button onClick={countUp}>+2</button>
{/* count を -1 したいことを伝える */}
<button onClick={countDown}>-1</button>
{/* state が変更されない(値がそのまま)のことを伝える */}
<button onClick={countStay}>stay</button>
{/* state を初期値 0 に戻したいことを伝える */}
<button onClick={countReset}>リセット</button>
</div>
);
}

Todo List
処理フロー
- “addTodoButton” のログが表示される
- Todo追加 ボタンをクリックし、”todo1”を入力し、OK をクリック
- “addTodoButton” と “MemoRemoveTodoButton: 0” のログが表示され、todo1と 削除 ボタンが 追加 される
- Todo追加 ボタンをクリックし、”todo2”を入力し、OK をクリック
- “addTodoButton” と “MemoRemoveTodoButton: 1” のログが表示され、todo2と 削除 ボタンが 追加 される
- todo2 の 削除 ボタンをクリックする
- ”removeTodo” と “addTodoButton” と のログが表示され、todo2と 削除 ボタンが 削除 される
- todo1 の 削除 ボタンをクリックする
- ”removeTodo” と “addTodoButton” と のログが表示され、todo1と 削除 ボタンが 削除 される
- removeTodo を useCallback でメモ化し、MemoRemoveTodoButton で memo 化することで、 不要な子コンポーネント の Todo の削除ボタン が 再レンダリングされるのを防ぐ ことができる
import { memo, useState, useCallback } from 'react';
// addTodoButton が レンダリング(描画)のたびに毎回表示
const AddTodoButton = ({ onAdd }) => {
console.log('addTodoButton');
return <button onClick={onAdd}>Todo追加</button>;
};
// MemoRemoveTodoButton: index のログが追加されたtodoのindexのみ表示(memo)
const MemoRemoveTodoButton = memo(({ index, onRemove }) => {
console.log('MemoRemoveTodoButton:', index);
return <button onClick={() => onRemove(index)}>削除</button>;
});
export default function UseReducerTodoList() {
const [todos, setTodos] = useState([]);
// 新しい todo を追加
const addTodo = () => {
console.log('addTodo');
const newTodo = prompt('追加するTodoを入力');
if (newTodo) setTodos((prev) => [...prev, newTodo]);
};
// 特定の todo を削除 (useCallback)
const removeTodo = useCallback((index) => {
console.log('removeTodo');
setTodos((prev) => prev.filter((_, i) => i !== index));
}, []); // 依存なしなので毎回同じ関数
return (
<div>
<h2>UseReducer TodoList</h2>
<AddTodoButton onAdd={addTodo} />
<ul>
{todos.map((todo, i) => (
<li key={i}>
{todo} <MemoRemoveTodoButton index={i} onRemove={removeTodo} />
</li>
))}
</ul>
</div>
);
}

まとめ
React Hooks の useReducer について、いかがでしたでしょうか?
useReducer は、複数の状態を持つコンポーネント において、複雑なstateの更新処理を reducer関数に集約し、コンポーネントの外に分離して管理するための Hooks で、状態遷移 や更新ルールが明確な処理 に向いています。
なお、単純な state の管理では、無理に useReducer を使わず、useState を選ぶ方が実装もシンプルになりおすすめです。
まずは基本的な使い方をしっかり押さえ、Todoリスト などの小さな コンポーネント から試しながら、少しずつ useReducer に慣れていきましょう。


コメント