React

【React】useReducerを使ったカスタムフックのサンプル

React
この記事は約7分で読めます。

はじめに

カスタムフックを使うことで、ステートの管理を簡潔にし、さらに再利用性の高い実装をすることができます。
また、コンポーネントの関心を少なくし見通しの良いコードになります。

今回はuseReducerを使用する例を紹介します。

わかりやすいように、3つの実装を順番に説明します。

  1. useStateを使用する例
  2. useReducerを使用する例
  3. useReducerを使用し、カスタムフックにする例

やりたいこと

以下のようなフォームがあるとします。
フォームの<input />4つに入力される値をステートで管理します。

export default function App() {
  return (
    <div>
      <input
        name="lastName"
      />
      <input
        name="firstName"
      />
      <input
        name="zipCode"
      />
      <input
        name="address"
      />
    </div>
  );
}

1. useStateを使用する例

同じような表現が繰り返し現れているように見えます。

`useState`を使用する例

export default function App() {
  const [lastName, setLastName] = useState("");
  const [firstName, setFirstName] = useState("");
  const [zipCode, setZipcode] = useState("");
  const [address, setAddress] = useState("");

  return (
    <div>
      <input
        name="lastName"
        value={lastName}
        onChange={(e) => setLastName(e.target.value)}
      />
      <input
        name="firstName"
        value={firstName}
        onChange={(e) => setFirstName(e.target.value)}
      />
      <input
        name="zipCode"
        value={zipCode}
        onChange={(e) => setZipcode(e.target.value)}
      />
      <input
        name="address"
        value={address}
        onChange={(e) => setAddress(e.target.value)}
      />
    </div>
  );
}

2. useReducerを使用する例

  • ステートはオブジェクトになり、見やすくなりました(感じ方には個人差あり)
  • onChangeのコールバックは、handleChange()に共通化できました
  • でも、ちょっと複雑に見えるかもしれない

`useReducer`を使用する例

const initialState = {
  lastName: "",
  firstName: "",
  zipCode: "",
  address: ""
};

export default function App() {
  const formReducer = (state, { name, value }) => ({ ...state, [name]: value });

  const [formState, formDispatch] = useReducer(formReducer, initialState);

  const handleChange = ({ target: { name, value } }) => formDispatch({ name, value });

  return (
    <div>
      <input
        name="lastName"
        value={formState.lastName}
        onChange={handleChange}
      />
      <input
        name="firstName"
        value={formState.firstName}
        onChange={handleChange}
      />
      <input
        name="zipCode"
        value={formState.zipCode}
        onChange={handleChange}
      />
      <input
        name="address"
        value={formState.address}
        onChange={handleChange}
      />
    </div>
  );
}

3. useReducerを使用し、カスタムフックにする例

Hooksは別ファイル(useForm.jsとします)に切り出し、再利用することができます。

先ほどの例と同じロジックですが、initialStateを引数にとり、

  • ステート(formState
  • ステートを更新する関数(handleChange

を返します。

useForm.js

import { useReducer } from "react";

const useForm = (initialState = {}) => {
  // [2.]と同じロジック
  const formReducer = (state, { name, value }) => ({ ...state, [name]: value });
  const [formState, formDispatch] = useReducer(formReducer, initialState);
  const handleChange = ({ target: { name, value } }) => formDispatch({ name, value });

  // 追加
  return [formState, handleChange];
};

export default useForm;

🐢

コンポーネントの関心は、initialStateだけになりました。
管理するステートが変わったとしても、簡単にフォームの追加や削除を行うことができます。

App.js

const initialState = {
  lastName: "",
  firstName: "",
  zipCode: "",
  address: ""
};

export default function App() {
  const [formState, handleChange] = useForm(initialState);

  return (
    <div>
      <input
        name="lastName"
        value={formState.lastName}
        onChange={handleChange}
      />
      <input
        name="firstName"
        ...以下略
      />
    </div>
  );
}

まとめ

  • useReducerを使うことで、冗長なuseStateの宣言を簡略化し、処理を共通化できる
  • カスタムフックにすることで、コンポーネントから関心を取り除くことができる