🐕

useState()と何が違う?useReducer()で “散らかった状態管理” を一元化!

2025/01/07に公開

🧩 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つのポイント

  1. useState()は単純な状態管理向け → 複数のsetState()が必要になる場面では煩雑になる。
  2. useReducer()は複雑な状態管理に最適 → 状態更新ロジックをReducer関数に一元化できる。
  3. dispatch()を使えば状態変更が明確化 → 状態遷移のフローが簡潔かつわかりやすくなる。

🔄 まとめ:useReducer()はチーム開発でも役立つ

特に複雑な状態管理が必要な場面やチーム開発では、useReducer()が役立ちます。

なぜなら、状態変更の流れがReducer関数に集約されることで、コードの保守性が高まり、チーム全体で状態遷移を把握しやすくなるからです。

言い換えると、useReducer()は単なる「useStateの代替」ではなく、 「状態管理を整理し、コードを簡潔に保つための思考法」 を提供してくれるのです。

Discussion