【React】useStateとuseReducerの違いとuseReducerの使い方と使い所

React

ReactのuseStateとuseReducerの違いとuseReducerの使い方を紹介します。

useStateとuseReducerの違い

まずは簡単なボタンを作成して、数字をカウントアップするときの書き方の違いをみてみましょう。

useStateとuseReducerの書き方の違い

useStateの場合の書き方

import { useState } from "react";

const Sample = () => {
  const [state, setState] = useState(0);
  const countUp = () => {
    setState((prev) => ++prev);
  };
  return (
    <>
      <p>カウント数: {state}</p>
      <button onClick={countUp}>+</button>
    </>
  );
};

export default Sample;

useReducerの場合の書き方

import { useReducer } from "react";

const Sample = () => {
  const [state, dispatch] = useReducer(prev=>++prev, 0);
  const countUp = () => {
    dispatch();
  };
  return (
    <>
      <p>カウント数: {state}</p>
      <button onClick={countUp}>+</button>
    </>
  );
};

export default Sample;

useReducerを使った場合、配列の0番目にはstateが渡ってきます。
この点はuseStateと同じです。
異なるのはここからで、配列の1番目にはdispatchが渡ってきます。
このdispatchを実行すると、useReducerに渡した第一引数の関数が実行されます。
useReducerに渡す第二引数は初期値を設定します。

useStateとuseReducerの状態の更新の仕方の違い

useStateはsetStateを実行する場所を利用者が選ぶことができます。
useReducerは、定義の時点で関数を決定します。
つまり、useStateは値を変更したいときに実行する場所を決めるのに対し、useReducerの場合は定義のときにどのように更新するのかまで決めます。
言い換えると、useStateは利用側が状態の更新の仕方を決定するのに対し、useReducerは、状態側で更新の仕方を決定する違いがあります。
useReducerでは利用者はあくまで更新の方法(type)を指定するだけで更新内容自体は決められません。

useReducerの使い所

useReducerの使い所は、大きなアプリケーションを作成する場合や、多数で開発する場合です。
個人開発ではuseStateだけでも問題ありませんが、他の開発者と協働した場合、useStateでは利用者側が更新の仕方を決定するため意図しない使われ方をする場合があります。
アプリケーションが大きくなる場合にuseReducerの使用のメリットが大きくなります。

useReducerのシンプルな使い方

useReducerは、上のように数字をカウントアップするだけといった、どのように更新するかというactionの部分が一つだけのパターンの場合はあまり使用しません。
以下のように2つ以上ある場合の書き方をみてみましょう。

import { useReducer } from "react";

const Sample = () => {
  const [state, dispatch] = useReducer((prev, action) => {
    switch (action) {
      case "+":
        return ++prev;
      case "-":
        return --prev;
      default:
        throw new Error("actionを正しく設定してください。");
    }
  }, 0);
  const countUp = () => {
    dispatch("+");
  };
  const countDown = () => {
    dispatch("-");
  };
  return (
    <>
      <p>カウント数: {state}</p>
      <button onClick={countUp}>+</button>
      <button onClick={countDown}>-</button>
    </>
  );
};

export default Sample;

useReducerの第一引数に渡す関数ですが、その関数に2つの引数を渡します。
状態のstateとactionというどのように処理内容を判定する変数です。
そのactionに渡ってくる値によって、カウントアップするのかカウントダウンするのかといったどのように更新するかを決定します。
渡すときは、dispatchに引数を設定して渡します。

useReducerの一般的な書き方(typeを設ける)

import { useReducer } from "react";

const Sample = () => {
  const [state, dispatch] = useReducer((prev, {type}) => {
    switch (type) {
      case "+":
        return ++prev;
      case "-":
        return --prev;
      default:
        throw new Error("actionを正しく設定してください。");
    }
  }, 0);
  const countUp = () => {
    dispatch({type: "+"});
  };
  const countDown = () => {
    dispatch({type: "-"});
  };
  return (
    <>
      <p>カウント数: {state}</p>
      <button onClick={countUp}>+</button>
      <button onClick={countDown}>-</button>
    </>
  );
};

export default Sample;

一般的には、dispatchするときにオブジェクトにして引数を渡します。
typeという変数を用意されることが多く、とてもreduxに近い書き方となります。

useReducerの一般的な書き方(type以外の引数を設ける)

処理内容部分にtype以外の引数を渡すこともできます。
以下が例です。

import { useReducer } from "react";

const Sample = () => {
  const [state, dispatch] = useReducer((prev, { type, step }) => {
    switch (type) {
      case "+":
        return ++prev;
      case "-":
        return --prev;
      case "+2":
      case "+3":
        return prev + step;
      case "clear":
        return 0;
      default:
        throw new Error("actionを正しく設定してください。");
    }
  }, 0);
  const countUp = () => {
    dispatch({ type: "+" });
  };
  const countTwoUp = () => {
    dispatch({ type: "+2", step: 2 });
  };
  const countThreeUp = () => {
    dispatch({ type: "+3", step: 3 });
  };
  const countDown = () => {
    dispatch({ type: "-" });
  };
  const countClear = () => {
    dispatch({ type: "clear" });
  };
  return (
    <>
      <p>カウント数: {state}</p>
      <button onClick={countUp}>+</button>
      <button onClick={countDown}>-</button>
      <button onClick={countTwoUp}>+2</button>
      <button onClick={countThreeUp}>+3</button>
      <button onClick={countClear}>クリア</button>
    </>
  );
};

export default Sample;

このようにすると、dispatchのときに渡すオブジェクトに好きなだけ引数を設定し、定義部分の関数で受け取りたい引数を指定して使うことができます。

useReducerの一般的な書き方(reducer部分を別で定義)

もちろん上のuseReducerの関数部分は引数を渡して使っているだけなので、以下のように切り離して定義することも可能です。

import { useReducer } from "react";

const reducer = (prev, { type, step }) => {
  switch (type) {
    case "+":
      return ++prev;
    case "-":
      return --prev;
    case "+2":
    case "+3":
      return prev + step;
    case "clear":
      return 0;
    default:
      throw new Error("actionを正しく設定してください。");
  }
};
const Sample = () => {
  const [state, dispatch] = useReducer(reducer, 0);
  const countUp = () => {
    dispatch({ type: "+" });
  };
  const countTwoUp = () => {
    dispatch({ type: "+2", step: 2 });
  };
  const countThreeUp = () => {
    dispatch({ type: "+3", step: 3 });
  };
  const countDown = () => {
    dispatch({ type: "-" });
  };
  const countClear = () => {
    dispatch({ type: "clear" });
  };
  return (
    <>
      <p>カウント数: {state}</p>
      <button onClick={countUp}>+</button>
      <button onClick={countDown}>-</button>
      <button onClick={countTwoUp}>+2</button>
      <button onClick={countThreeUp}>+3</button>
      <button onClick={countClear}>クリア</button>
    </>
  );
};

export default Sample;

このreducer部分をまとめて管理したりできるので、アプリケーションが大きくなるにつれてコードの可読性がuseStateよりも向上します。

React

Posted by devsakaso