useState()と何が違う?useReducer()で “散らかった状態管理” を一元化!
🧩 useReducer()を使いこなす
「useReducer()って、useState()の上位互換ってことでしょ?」と思っているなら、ちょっと待った!
useReducer()はただの「拡張版」ではなく、“複雑な状態管理を一元化する”ための仕組みです。言い換えると、複数の状態変更ロジックをひとつのReducer関数に集約し、無駄を省く効率化ツールです。
この違いを理解するために、useState()とuseReducer()の使い方の「ギャップ」に注目して解説していきます。
💡 useState()の限界:状態管理が複雑になる瞬間
例えば、こんな場面を想像してください:
あなたは、ブログ投稿のデータ取得処理を管理するコンポーネントを作成しています。
useState()で各状態を管理すると、次のようなコードになります。
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState(null);
async function fetchPosts() {
setIsLoading(true);
setError(null);
try {
const response = await fetch('https://dummyapi.io/data/v1/post');
if (!response.ok) throw new Error('Failed to fetch posts.');
const posts = await response.json();
setData(posts);
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
}
見た目はシンプルですが、状態管理が散らかっているのがわかりますか?
- 3つの状態(data, isLoading, error)を個別に管理している。
- 各状態変更に対して、複数のsetState()呼び出しが必要。
- 状態の更新ロジックが分散しているため、一貫性が欠ける。
🚀 useReducer()の登場:状態管理を一元化
ここでuseReducer()を使うと、状態管理が劇的に整理されます。
✅ Reducer関数で状態管理を一元化
const initialHttpState = {
data: null,
isLoading: false,
error: null,
};
function httpReducer(state, action) {
switch (action.type) {
case 'FETCH_START':
return { ...state, isLoading: true, error: null };
case 'FETCH_SUCCESS':
return { ...state, isLoading: false, data: action.payload };
case 'FETCH_ERROR':
return { ...state, isLoading: false, error: action.payload };
default:
return state;
}
}
Reducer関数で、状態の更新ロジックをひとつの場所に集約します。
これにより、「どのアクションがどの状態を変えるのか」が明確になります。
✅ useReducer()で状態とdispatchを取得
const [httpState, dispatch] = useReducer(httpReducer, initialHttpState);
ここで得られるのは:
- httpState:現在の状態(data, isLoading, error のオブジェクト)
- dispatch:アクションをReducer関数に送るための関数
✅ dispatch()で状態を変更
useReducer()の肝はdispatch()にあります。 状態変更は、「dispatch()を呼び出してアクションを送信するだけ」 で完了します。
例えば、データ取得処理は以下のように書けます。
async function fetchPosts() {
dispatch({ type: 'FETCH_START' });
try {
const response = await fetch('https://dummyapi.io/data/v1/post');
if (!response.ok) throw new Error('Failed to fetch posts.');
const posts = await response.json();
dispatch({ type: 'FETCH_SUCCESS', payload: posts });
} catch (error) {
dispatch({ type: 'FETCH_ERROR', payload: error.message });
}
}
✅ 状態の流れを整理すると…
アクション名 | 処理内容 | 状態の変化 |
---|---|---|
FETCH_START | データ取得開始 | isLoading: true, error: null |
FETCH_SUCCESS | データ取得成功 | data: posts, isLoading: false |
FETCH_ERROR | データ取得失敗 | error: message, isLoading: false |
🤔 何が「美しい」のか?
useReducer()の美しさは、状態の更新ロジックがひとつのReducer関数に集約されていることです。
- useState()の場合 → 状態管理が分散して、複数のsetState()呼び出しが必要
- useReducer()の場合 → 状態管理がReducer関数に集約され、dispatch()で簡潔に管理
つまり、「状態管理を1つの窓口に集約」 することで、コードがスッキリし、状態遷移がわかりやすくなります。
🧠 useReducer()は「if-else文」と似ている?
useReducer()の構造をもっと簡単にイメージすると、「if-else文で条件分岐して状態を変える」 感覚に近いです。
function httpReducer(state, action) {
if (action.type === 'FETCH_START') {
return { ...state, isLoading: true, error: null };
}
if (action.type === 'FETCH_SUCCESS') {
return { ...state, data: action.payload, isLoading: false };
}
if (action.type === 'FETCH_ERROR') {
return { ...state, error: action.payload, isLoading: false };
}
return state;
}
🎯 結論:useReducer()の3つのポイント
- useState()は単純な状態管理向け → 複数のsetState()が必要になる場面では煩雑になる。
- useReducer()は複雑な状態管理に最適 → 状態更新ロジックをReducer関数に一元化できる。
- dispatch()を使えば状態変更が明確化 → 状態遷移のフローが簡潔かつわかりやすくなる。
🔄 まとめ:useReducer()はチーム開発でも役立つ
特に複雑な状態管理が必要な場面やチーム開発では、useReducer()が役立ちます。
なぜなら、状態変更の流れがReducer関数に集約されることで、コードの保守性が高まり、チーム全体で状態遷移を把握しやすくなるからです。
言い換えると、useReducer()は単なる「useStateの代替」ではなく、 「状態管理を整理し、コードを簡潔に保つための思考法」 を提供してくれるのです。
Discussion