ReactのuseReducerを理解する

前回の記事ではReactのuseStateの正しい使い方を説明しました。

今回はReact Hooksのうちの一つであるuseReducerの使い方を説明します。

reducerとは

まずは、JavaScriptのreduceメソッドについておさらいしましょう。

reduceは英語で減らすという意味ですね。イメージとして、配列の複数データを操作して1つの値に減らすためのメソッドであると考えてください。

useReducerも同じようにState(状態)を管理できるメソッドになります。

配列の数字をループして加算したい場合はこのようにできます。

const numbers = [10, 20, 30];

let total = 0;
for (const n of numbers) {
  total += n;
}

export default function App() {
  return <div className="App">{total}</div>;
}

これで、ブラウザに合計の60が表示されました。

同じことをreduceメソッドを使って行ってみます。

reduceメソッドは2つのパラメータを受け取ります。

reduce(()=>,0)

最初のパラメータは関数で、2つ目が初期値(スタートポイント)になります。

// import { useState } from "react";

const numbers = [10, 20, 30];

let total = 0;
function reduceNum() {
  total = numbers.reduce(
    (accumulator, currentValue) => accumulator + currentValue,
    0
  );
  return total;
}
reduceNum();

export default function App() {
  return <div className="App">{total}</div>;
}

これで同じように合計値の60がブラウザに表示されました。

reduce()関数の最初に使う関数では①accumulatorと②currentValueがパラメータとして使えます。

accumulatorはaccumulate(蓄積する)という意味です。つまり、今までの数値を累積(足した)値になります。いわば、前回までの累積値といえます。

2番目に来るcurrentValueは現在の値です。このようにreduceメソッドでは前回までの累積値と現在の値をどうするか、指示することができます。

今回の場合は単純に配列の前回の値(0番目)と現在地(1番目)を足すという指示をしています。

その次に配列の前回の値(0と1番目の累積値)と現在地(2番目)を足す。これを繰り返しているだけです。

これでreduceメソッドの使い方が理解できましたね。

useReducer

ReactのuseReducerは基本はこのようになります。

const [state, dispatch] = useReducer(reducer, initialArg, init?)

useReducerからは2つの値が返ってきます。それを慣例的にstateと、dispatchと名称を付けて取っておきます。

useReducerがreturn返す値

state:現在の状態。最初のレンダーでは、初期値のinitialArg、もしくはinit(initialArg)が返ってきます。

dispatch:どのようにコンポーネントを再レンダーし、stateを更新するか指示する関数

useReducer関数の引数について

reducer:reducerでは、stateがどのようにアップデートされたいのか指示することができます。

initialArg:デフォルト値(データタイプに指定はありません。)

init?:オプショナルの引数です。もしinitialArgに何かしらの関数を実行したい場合はここで指示できます。その場合はinit(initialArg)になります。もし空の場合は、initialArgはデフォルト値として渡されます。

useReducerの実例

では下記の実例を見てみましょう。

import { useReducer } from "react";

export default function App() {
  function reducer(state, action) {
    switch (action.type) {
      case "SET_NAME":
        return { ...state, name: action.payload };
    }
  }

  const intialArg = {
    names: [],
    name: "",
  };

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

  return (
    <div className="App">
      <input
        type="text"
        value={state.name}
        onChange={(e) =>
          dispatch({ type: "SET_NAME", payload: e.target.value })
        }
      />
      <div>name = {state.name}</div>
    </div>
  );
}

このようにinputへの入力と同時にstate.nameの状態が更新されコンポーネントが再度レンダーされました。

inputに入力されれるたびにonChangeのdispatchが発火されpayload(データ)が更新されます。dispatchはuseReducerのstateを更新する役目があるので、コンポーネントが更新されたわけですね。

次に、ボタンを作成して、state.namesにnameのデータを追加していきましょう。

import { useReducer } from "react";

export default function App() {
  function reducer(state, action) {
    switch (action.type) {
      case "SET_NAME":
        return { ...state, name: action.payload };
      case "ADD_NAME":
        return {
          ...state,
          names: [...state.names, state.name],
          name: "",
        };
    }
  }

  const intialArg = {
    names: [],
    name: "",
  };

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

  return (
    <div className="App">
      <input
        type="text"
        value={state.name}
        onChange={(e) =>
          dispatch({ type: "SET_NAME", payload: e.target.value })
        }
      />
      <div>name = {state.name}</div>
      <button onClick={() => dispatch({ type: "ADD_NAME" })}>Add Name</button>

      <div>
        {state.names.map((name, index) => (
          <div key={index}>{name}</div>
        ))}
      </div>
    </div>
  );
}

…のスプレッド構文を使って、配列に現在のstateを残したまま、新しいデータを追加しています。

これで、データの入力時に今までのデータを付けるので”追加”できるようになります。

このようにuseReducerを使うことで複雑なstate(状態)の管理が可能になります。