iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

useReducerの使い方

useReducerとは?

useReducerは、Reactのフックの一つで、現在の状態 (state) とアクション (action) を受け取って新しい状態を返すリデューサー関数を使用します。これにより、状態管理のロジックを外部に持たせることができ、テストがしやすくなるという利点があります。

reducerは、現在のstateactionを受け取って新しいstateを返す関数です。この考え方はuseReducerでも使われており、状態管理ライブラリであるReduxでも一般的に使用されています。

useReducer – React

基本的な構文

  • reducer: 現在の状態とアクションを受け取って新しい状態を返す関数。
  • initialArg: 初期状態。
  • init: オプショナルな初期化関数。(initialArgを引数に取り初期値を生成する関数)
  • statedispatchが返される
const [state, dispatch] = useReducer(reducer, initialArg, init?)

useReducerを使用したカウンターの例

import { useReducer } from "react";

const reducer = (state, action) => {
  if (action.type === "incrementedAge") {
    return {
      age: state.age + 1,
    };
  }
  throw Error("エラーが発生しました");
};

const Counter = () => {
  const [state, dispatch] = useReducer(reducer, { age: 0 });

  return (
    <>
      <button
        onClick={() => {
          dispatch({ type: "incrementedAge" });
        }}
      >
        Increment age
      </button>
      <p>{`私の年齢は${state.age}歳です`}</p>
    </>
  );
};

export default Counter;
  1. ボタンをクリックするとdispatch{ type: "incrementedAge" }というアクションを発行します。
  2. reducer関数がこのアクションを受け取り、状態を更新します。
  3. 初回のstateには初期値の { age: 0 }が設定されます。

複雑なロジックになる場合は、reducerを別ファイルに分けることで管理しやすくなります。

useStateとの違い

useStateuseReducerはどちらも状態管理のためのフックですが、どのような違いがあるのかを見ていきたいと思います。

useStateを使用したカウンターの例

import { useState } from "react";
const Counter = () => {
  const [count, setCount] = useState(0);

  return (
    <div>
      {count}
      <button onClick={() => setCount(count + 1)}>+</button>
      <button onClick={() => setCount(count - 1)}>-</button>
    </div>
  );
};

export default Counter;

useReducerを使用したカウンターの例

import { useReducer } from "react";

const Counter = () => {
    // 初期値は0でactionにdispatchの引数がactionの値としてReducer関数に渡される
  const [state, dispatch] = useReducer((state, action) => state + action, 0);

  return (
    <div>
      {state}
      <button onClick={() => dispatch(1)}>+</button>
      <button onClick={() => dispatch(-1)}>-</button>
    </div>
  );
};

export default Counter;

useReducerを使うメリットは、状態管理のロジックをコンポーネントの外に抽出できることです。これによってコンポーネント内部がスッキリさせることができ、テストのしやすさも向上されることが可能になります。

チェックボックスの例

useStateを使用した場合

import { useState } from "react";

const Checkbox = () => {
  const [checked, setChecked] = useState(false);

  return (
    <div>
      <input
        type="checkbox"
        checked={checked}
        onChange={() => setChecked(!checked)}
      />
    </div>
  );
};

export default Checkbox;

useReducerを使用した場合

import { useReducer } from "react";

const Checkbox = () => {
  const [checked, toggle] = useReducer((checked) => !checked, false);

  return (
    <div>
      {/* toggleが呼ばれるとuseReducerの第一引数に渡した関数が呼ばれる */}
      <input type="checkbox" checked={checked} onChange={toggle} />
    </div>
  );
};

export default Checkbox;

入力フィールドの例

useStateを使用した場合

import { useState } from "react";

const TaxCalculator = () => {
  const [value, setValue] = useState("0円");

  return (
    <div>
      {value}
      <input
        type="number"
        onChange={(e) => setValue(`${Math.round(e.target.value * 1.08)}円`)}
      />
    </div>
  );
};

export default TaxCalculator;

useReducerを使用した場合

import { useReducer } from "react";

const TaxCalculator = () => {
  const [value, setValue] = useReducer(
    // コンポーネント内で現在の状態は使用しないので、第一引数は不要
    (_, action) => `${Math.round(action * 1.08)}円`,
    "0円"
  );

  return (
    <div>
      {value}
      <input type="number" onChange={(e) => setValue(e.target.value)} />
    </div>
  );
};

export default TaxCalculator;

状態管理をuseReducerにまとめる

以下のように状態管理が多く、可読性が悪くなってしまう場合があると思います。

ここでuseReducerを使用して状態をまとめることで、管理しているものや初期値を分かりやすくすることができます。

const App = () => {
  const [isOpen, setIsOpen] = useState(false)
  const [type, setType] = useState('large')
  const [phone, setPhone] = useState('')
  const [email, setEmail] = useState('')
  const [error, setError] = useSatte(null)

  return (
    <></>
  )
}
import { useReducer } from "react";

const initialState = {
  isOpen: true,
  type: "",
  phone: "",
  email: "",
  error: "",
};

const reducer = (state, action) => {
  switch (action.type) {
    case "SET_PHONE":
      return { ...state, phone: action.payload }; // 特定のプロパティだけ更新
    case "SET_EMAIL":
      return { ...state, email: action.payload };
    default:
      return state;
  }
};

const ReducerComponent = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <p>電話番号: {state.phone}</p>
      <p>メールアドレス: {state.email}</p>
      <button
        onClick={() =>
          dispatch({ type: "SET_PHONE", payload: "090-0000-0000" })
        }
      >
        電話番号
      </button>
      <button
        onClick={() =>
          dispatch({ type: "SET_EMAIL", payload: "sample@example.com" })
        }
      >
        メールアドレス
      </button>
    </>
  );
};

export default ReducerComponent;

useStateとの使い分けは?

useStateuseReducerで実装されているので、useStateでできることはuseReducerでも可能なようです。

使い分けについては、useStateuseReducerドキュメントには個人の好みとあるとありますが、単純な状態管理にはuseStateを使い、複数の状態が依存しているよう複雑な状態ロジックなどにはuseReducerを使うのも良いのかなと思いました。

state ロジックをリデューサに抽出する – React

まとめ

useReducerを適切に使うことができればコードをよりシンプルに管理できることが分かりました。

今後はuseReducerの型の確認と、useStateからuseReducerを作ったりなどして理解を深めていきたいなと思いました。

最後まで読んでくださりありがとうございます! この記事を読んで興味を持って下さった方がいらっしゃればカジュアルにお話させていただきたく、是非ご応募をお願いいたします!!Wantedly / Green