UseReducerの基本的な使い方

2024/07/05に公開

はじめに

UseReducerUseStateと同じようにコンポーネントの中にstateと呼ばれる状態初期値を定義するところまでは同じ。
UseStateと異なるのは定義した時点で状態の値に対して、どんな更新を行うのか定義できるところ。

使用例

import { useState, useReducer } from "react"

const Example = () => {
  // (2)
  const rFunction = (prev, {action, value}) => {
    switch(action) {
      case "up":
        return prev + value;
      case "down":
        return prev - value;
      default:
        throw new Error("定義されていないactionです。");
    }
  };

  // (1)
  const [rstate, dispatch] = useReducer((prev, { action, value }) => {
    rFunction(prev, { action, value })
  }, 0);

  // (3)
  const rAddCount = (value) => {
    // 初期値が決められた段階で既にどんな更新を行うのか定義済み
    dispatch({ action: "up", value: value });
  };
  const rMinusCount = (value) => {
    dispatch({ action: "down", value: value });
  }



  return (
    <>
      <div>
        <h3>{ rstate }</h3>
        <button onClick={ () => rAddCount(1) }> +1 </button> <!-- (4) -->
        <button onClick={ () => rAddCount(2) }> +2 </button>
        <button onClick={ () => rMinusCount(1) }> -1 </button>
        <button onClick={ () => rMinusCount(2) }> -2 </button>
      </div>
    </>
  );
};

export default Example;

(1)定義

const [rstate, dispatch] = useReducer((prev, { action, value }) => {
  rFunction(prev, { action, value })
}, 0);

useReducerを呼び出した後、第一引数に更新を実行する関数を、第二引数に初期値を設定。
第一引数に渡す更新処理が返り値のdispatchメソッドを呼び出した時に行う処理になる。

今回の場合、
関数は(prev, { action, value }) => { rFunction(prev, { action, value }) }の部分。
初期値は0

関数についてprevは前の状態の値。{ action, value }dispachメソッドが引数として受け取る値。

dispachメソッドが呼ばれた後、内部でrFunctionメソッドが呼ばれる。書き方によってはここに直接、処理をアロー関数で記述してしまっても問題ない。

(2)実際の更新処理

const rFunction = (prev, {action, value}) => {
  switch(action) {
    case "up":
      return prev + value;
    case "down":
      return prev - value;
    default:
      throw new Error("定義されていないactionです。");
  }
};

(prev, { action, value }) => { rFunction(prev, { action, value }) }の記述部分でrFunctionprev(前の状態の値)とaction(処理)とvalue(前の状態の値に与える影響)のプロパティを持つオブジェクトを渡す。

action(処理)を引数に渡す処理は今の所よく見受けられるが、value(前の状態の値に与える影響)を渡して良いものかまだ不明。

処理内容としてはactionupならvalue分、加算。actiondownならvalue分、減算。

(3)dispatchの利用

const rAddCount = (value) => {
  // 初期値が決められた段階で既にどんな更新を行うのか定義済み
  dispatch({ action: "up", value: value });
};

const rMinusCount = (value) => {
  dispatch({ action: "down", value: value });
}

...中略
<button onClick={ () => rAddCount(1) }> +1 </button>
<button onClick={ () => rMinusCount(1) }> -1 </button>

ボタンを押した際に() => rAddCount(1)、もしくは() => rMinusCount(1)を実行。
それぞれのメソッドの内部でdispatchメソッドにactionvalueを渡して、さらに内部のrFunctionを呼び出している。

こんな書き方をしても良いのか?

const countControle = (action, value) => {
  dispatch({ action: action, value: value });
};

...中略
<button onClick={ () => countControle("up", 1) }> +1 </button>
<button onClick={ () => countControle("down", 1) }> -1 </button>

さらに

直接dispatchを呼んでも。
これが一番わかりやすいかも?

<button onClick={ () => dispatch({ action: "up", value: 4 }) }> +4 </button>

おまけ... (4)イベントハンドラーに渡す引数を渡す時

return (
  <>
    <div>
      <h3>{ rstate }</h3>
      <button onClick={ () => rAddCount(1) }> +1 </button> <!-- (4) -->
      <button onClick={ () => rAddCount(2) }> +2 </button>
      <button onClick={ () => rMinusCount(1) }> -1 </button>
      <button onClick={ () => rMinusCount(2) }> -2 </button>
    </div>
  </>
);

今まで<button onClick={rMinusCount}> -1 </button>の書き方をしていたが
引数を渡したい時には<button onClick={ () => rMinusCount(1) }> -1 </button>のように中でアロー関数を定義し、さらにその内部でrMinusCountメソッドを引数付きで呼び出せば良い。

UseStateとの違い

  • UseStateは初期値の定義のみだが、UseReducerは初期値に加えて更新方法も定義時に指定することができる。
    • 定義している箇所が一箇所なのでコードの見通しが良くなる。
    • 更新方法が管理しやすくなるので開発規模が大きくなった時に威力を発揮する。

Discussion