React の useReducer フックの使い方を具体例をまじえて解説【 TypeScript 】

ショウヘイ

どうも、ノマドクリエイターのショウヘイ(@shohei_creator)です。

ふーちゃん

ブログアシスタントのふーちゃんです。

React を使って何かしらのアプリを作る時には、たいていの場合は、入力値などの state (変数の状態)を管理することになります。

React には、 state を管理できる便利な useState フックが用意されています。しかし、アプリの開発規模が大きくなればなるほど、 setState メソッドの記述がちらばりやすくなるという欠点があります。また、 state の更新が簡単な反面、実装者の不注意でバグが混入しやすくなるという危険性もひそんでいます。

そこで、 React には、 useState フックよりも厳格に state を管理できる useReducer フックが用意されています。 useReducer フックは、 state の更新方法を事前に定義しておけるため、想定外の state の更新が起きません。また、 state を更新する関数を一元化できるため、コードの保守性が良くなります。

この記事では、 React の useReduer の使い方について説明します。

目次

React の useReducer フックとは

React の useReducer フックは、 state を管理できる機能を持っています。

useReducer フックは、 state を事前に定義した方法でしか更新できず、かつ更新に使う関数を一元管理できるため、より規模の大きいアプリ開発に適しています。

また、 useReducer フックによって生成される dispatch メソッドは、 useContext フックのコールバック関数として扱えます。深層のコンポーネントにも dispatch メソッドを届けられるので、コンポーネント全体で state を容易に管理できるようになります。

ふーちゃん

useReducer フックの概要を見たかぎりでは、 useState フックの上位互換といった印象ですね。

ショウヘイ

機能面では上位互換だけれど、機能が豊富になる分だけ、コードの記述量が多くなってしまうという欠点もあるんだ。

小規模のアプリ開発なら、 useState フックを使った方が合理的だね。


React の useReducer フックの基本構文

React の useReducer フックを使う時には、 2 つの引数を設定します。

useReducer フックの第 1 引数には、実行したい関数を設定します。この関数では、 swith と case による分岐をおこなうことになります。また、更新方法ごとの処理も書くことになり、コード量が多くなります。どこかで名前付き関数として定義しておき、引数に設定した方が利口です。

useReducer フックの第 2 引数には、 state の初期値を設定します。数字や文字列などの簡単な値なら、第 2 引数にベタ書きでも構いません。オブジェクト(連想配列)のような複雑な構造を初期値にしたいなら、いったん変数に代入しておき、 引数に設定した方が利口です。 

こちらは、 JavaScript で reducer 関数と useReducer フックを書いた場合のコードです。 

// reducerの名前付き関数
const reducer = (state, action) => {
  switch (action.type) {
    case "case1":
      return updateState;
    case "case2":
      return updateState;
    case "case3":
      return updateState;
    default:
      throw new Error();
  }
};

// useReducerフック
const [state, dispatch] = useReducer(reducer, initialState);

 こちらは、 TypeScript で reducer 関数と useReducer フックを書いた場合のコードです。

reducer 関数では、名づけの直後に React.Reducer<State, Action> で型宣言します。また、引数と返り値の方にも、型宣言しておきます。

// reducerの名前付き関数
const reducer: React.Reducer<State, Action> = (
  state: State,
  action: Action
): State => {
  switch (action.type) {
    case "case1":
      return updateState;
    case "case2":
      return updateState;
    case "case3":
      return updateState;
    default:
      throw new Error();
  }
};

// useReducerフック
const [state, dispatch] = useReducer(reducer, initialState);
ショウヘイ

実際に useReducer フックを使う時は、 react ライブラリから import することを忘れないでください。

import React, { useReducer } from "react";

React の useReducer フックの使い方

React の useReducer フックを使って、実際に reducer 関数を経由して state を更新する流れを説明します。参考例として、数字を増減させるカウンターを用意しました。

まずは、コード全文を概観してください。また、カウンターのボタンを押して、実際に数値が増減することも確かめてください。

import React, { useReducer } from "react";

type State = {
  count: number;
};

type Action =
  | { type: "increment" }
  | { type: "decrement" }
  | { type: "reset"; payload: State };

const initialState: State = {
  count: 0
};

const reducer: React.Reducer<State, Action> = (
  state: State,
  action: Action
): State => {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    case "reset":
      return action.payload;
    default:
      throw new Error();
  }
};

export const Counter: React.VFC = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <p>Count: {state.count}</p>
      <button
        onClick={() => dispatch({ type: "reset", payload: initialState })}
      >
        Reset
      </button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
};

コードの上から順に説明していきます。まずは、 reducer 関数で使う変数の型宣言です。

State 型は、カウンターの数値を意味します。数値を取り扱うので、 number 型をつけました。

Action 型では、 reducer 関数の中にある『 state 更新方法の名前』を型名に使って、ユニオン型にしています。こうしておくと、 dispatch メソッドに渡されるオブジェクトの値が「 increment 」・「 decrement 」・「 reset 」以外だと警告が出るので、打ち間違いに気づきやすくなります。

// stateの型宣言(カウンターの数値)
type State = {
  count: number;
};

// Action名の型宣言
type Action =
  | { type: "increment" }
  | { type: "decrement" }
  | { type: "reset"; payload: State };

次に、 useReducer フックの第 2 引数に渡す初期値です。先ほど定義した State 型をつけました。

// useReducerに渡す初期値
const initialState: State = {
  count: 0
};

今度は、 reducer 関数です。

まずは、関数名「 reducer 」の直後に型宣言します。 React.Reducer<> としておき、 <> の中身に state の型( State 型)と Action 名の型( Action 型)を置きます。

引数の state と action にも、あらためて State 型と Action 型をつけます。また、アロー関数の直前にも State 型をつけて、返り値を型宣言します。

switch 文では、 Action 名によって case 分岐させます。どの case にも合致しなかった場合に備えて、 default としてエラーを生じるようにコードを書いておきます。

dispatch メソッドに渡した第 1 引数は、 state オブジェクトに格納されます。 state オブジェクトの count プロパティを参照するために、 return 文の中で state.count と書いています。

dispatch メソッドに渡した第 2 引数は、 action オブジェクトに格納されます。 dispatch メソッドに渡す引数は、キー名「 type 」のプロパティを持つオブジェクトなので、 switch (action.type) と書いています。

// reducer関数
const reducer: React.Reducer<State, Action> = (
  state: State,
  action: Action
): State => {

 // Action名によって分岐して、stateを更新する
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };
    case "decrement":
      return { count: state.count - 1 };
    case "reset":
      return action.payload;
    default:
      throw new Error();
  }
};

最後に、関数コンポーネントの本体です。

まずは、冒頭で useReducer フックに引数を渡して、 state と dispatch メソッドを生成します。

各種のボタンには、クリックによって dispatch メソッドが実行されるようにしておきます。 dispatch メソッドの引数には、オブジェクト(キー名「 type 」の値として、 action 名( state の更新方法)を設定)を渡します。

もしも、 state の更新時に別の値が必要になる場合には、引数のオブジェクトにプロパティ(キー名「 payload 」の値として、任意の値を設定)を追加します。

export const Counter: React.VFC = () => {

  // useReducerフックにreducer関数と初期値を渡して、
  // stateとdispatchメソッドを生成する。
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <>
      <p>Count: {state.count}</p>

      // 各種ボタンのクリックイベント発生時に、dispatchメソッドを実行する。
      <button
        onClick={() => dispatch({ type: "reset", payload: initialState })}
      >
        Reset
      </button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
};

React の useState フックと useReducer フックの違い

React の useState フックと useReducer フックは、どちらも state を管理する機能を持っています。

useState フックは、短いコードで state を管理できることが利点です。気軽に使える反面、アプリ開発の規模が大きくなればなるほど、あちらこちらに setState メソッドがちらばりやすくなるので、コードの保守性に難があります。また、簡単に state を更新できてしまうので、実装者の注意不足による想定外の state 更新がおこなわれる危険性もひそんでいます。

userReducer フックは、コードの記述量は多くなりますが、 state の更新方法を事前に定義できます。想定外の state の更新が起きにくくなるので、バグの混入を防ぎやすいです。また、 state の更新する関数を reducer 関数の中に一元管理しておけるので、コードの保守性に優れています。


ショウヘイ

小規模のアプリ開発なら、 state の管理は、 useState フックで充分ですね。

アプリ開発の規模が大きくなりそうなら、 useReducer フックの使用を検討してください。

この記事を知り合いに共有する
URLをコピーする
URLをコピーしました!
目次
閉じる