【React Hooks】useReducer 備忘録

2021/04/17に公開

Zennデビューになります🙋‍♂️
最近、開発でRedux Toolkitを使い始めました。
ピュアなReduxよりいろいろ簡略化して記述できるので便利で良いですね。

useReducerでの状態管理

Reactを学び始めた当時、状態管理にはuseStateばかり使っていて

  • useReducerって何?
  • なんで引数を2つとるの?
  • dispatchは何をしているの?
  • useStateだけじゃ駄目?

といった感じでした。現在Reactの状態管理方法は色々ありますが、今回はuseReducerの使い方を今更ながら簡単に備忘録を残そうかなと思います。

そもそもuseReducerとは🤔

useReducerはuseStateの代替として扱えるものです。なのでuseStateのみでもアプリなど動作させることは一応可能です。useReducerを使った方が好ましいのは、複雑なstateロジックがあるような時です。

全体のコード

解説する全体のコードとファイルの構造を先にお見せします。
create-react-appコマンドで作成していることを前提にしています。

ファイル構造

src
 |- App.js <- 今回の作業ファイル
 |- index.js
 |- reducer
   |- reducer.js <- 今回の作業ファイル

コード

App.js
import { useReducer } from "react";
import { initialState, reducer } from "./reducer/reducer";

export default function App() {
  const [state, dispatch] = useReducer(reducer, initialState);

  const ChangeStatus = () => [
    dispatch({
      type: "CHANGE_STATUS",
      payload: !state.status
    })
  ];

  const ChangeName = (e) => {
    dispatch({
      type: "CHANGE_NAME",
      payload: e.target.value
    });
  };

  const ChangeAge = (e) => {
    dispatch({
      type: "CHANGE_AGE",
      payload: e.target.value
    });
  };

  return (
    <div className="App">
      <ul>
        <li>status: {state.status ? "True" : "False"}</li>
        <li>user.name: {state.user.name || "No userName"}</li>
        <li>user.age: {state.user.age}</li>
      </ul>
      <form>
        <div>
          <label>ステータス:</label>
          <input
            type="checkbox"
            checked={state.status}
            onChange={ChangeStatus}
          />
        </div>
        <div>
          <label>名前:</label>
          <input value={state.user.name} onChange={ChangeName} />
        </div>
        <div>
          <label>年齢:</label>
          <input type="number" value={state.user.age} onChange={ChangeAge} />
        </div>
      </form>
    </div>
  );
}
reducer.js
export const initialState = {
  status: true,
  user: {
    name: "",
    age: 0
  }
};

export const reducer = (state, action) => {
  switch (action.type) {
    case "CHANGE_STATUS":
      return {
        ...state,
        status: action.payload
      };
    case "CHANGE_NAME":
      return {
        ...state,
        user: {
          ...state.user,
          name: action.payload
        }
      };
    case "CHANGE_AGE":
      return {
        ...state,
        user: {
          ...state.user,
          age: action.payload
        }
      };
    default:
      return state;
  }
};

Codesandbox

解説

まずuseReducerは引数を2つとります。

const [state, dispatch] = useReducer(reducer, initialState);
  • reducer
    • reducerは特定のactionに対してstateをどう変更させるか設定します。
    • 引数にstateとactionの2つの引数をとります。
  • initialState
    • useState同様、初期値を設定します。
      今回はreducerを記述したファイルと一緒に書いています。

ステートの参照方法

useReducerで定義したstateから値の参照をします。

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

return (
  <div>
    {state.status} {/* 初期値のstatusの値を参照しています */}
  </div>
)

ステートの変更方法

dispatchを使ってreducerにactionを送ることでステートを変更します。
オブジェクトにtypeとpayloadを定義します。これがactionです。
定義したdispatchのイベントはuseStateと同じようにonChange等を使うことによってreducerにactionを送ります。

const ChangeName = (e) => {
  dispatch({
    type: "CHANGE_NAME",
    payload: e.target.value
  });
};

そしてaction.typeに定義した内容によってreducer側で記述した変更をしてくれます。
action.typeが"CHANGE_NAME"の場合はuser.nameを変更します。
またここでスプレット構文を使用していますが、こうすることで今あるステートの値を展開してから変更内容を上書きすることができます。

export const reducer = (state, action) => {
  switch (action.type) {
    case "CHANGE_NAME":
      return {
        ...state,
        user: {
          ...state.user,
          name: action.payload
        }
      };
    default:
      return state;
  }
};

action.typeのタイプミスを防ぐ

今回のサンプルコードでは記述していませんでしたが、action.typeを以下のように定義することでタイプミスをしていたとしてもエラーで知らせてくれます。

reducer.js
export const CHANGE_NAME = "CHANGE_NAME";
App.jsx
import { CHANGE_NAME } from "./reducer/reducer.js";

簡単な備忘録でした。読んでいただきありがとうございました🙇‍♂️

Discussion