React学習 React文法

【React Hooks基礎】useReducerを理解する

本記事ではReact Hooksの一つのuseReducer()について詳細に解説いたします。

少しでも皆さんの理解につながれば嬉しいです。

useReducer()はuseState()と似ている

useReducer()はuseState()と同じく関数コンポーネントで状態を管理するために使用されます。

しかしuseReducer()は管理する状態が2つ以上などより複雑な状態の変更を管理する時にuseState()の代わりに使われます。

useReducer()とuseState()をどう使い分けるかの判断基準はこうなります。

  • 状態の更新が単純な実装ではuseState()で十分
  • 状態の更新がより複雑な実装ではuseReducer()を検討
  • 多くの実装ではuseState()で事足りるので、基本はuseState()を使いつつ、状態管理がややこしくなったら、useReducer()でスッキリ書けないかを考える

以上を踏まえてuseReducer()の解説を始めます。

構文

useReducer()の基本構文です。

const [state, dispatchFn] = useReducer(reducerFn, initialState);

useReducer()は2つの引数を受け取ります。(右辺)

第一引数はreducer関数で情報の更新方法を定義するために使用されます。

第二引数は初期状態を指定するために使用されます。

またuseReducer()は「state」と「dispatch関数」の2つの値を返します。(左辺)

stateは現在の値です。

dispatch関数はactionというオブジェクト作ります。

dispatch関数で作られたactionは即座にreducer関数に渡されます。

reducer関数はactionの値を利用してstateの状態を更新します。

actionって何?

いきなり出てきたactionについて解説します。

actionはdispatch関数で作るオブジェクトで、即座にreducer関数に渡されます。

自由にkeyとvalueを決められるのですが、慣例で「type」というプロパティを設定することになっていますが値は自由に決められます。

そしてactionには必要に応じて新たにプロパティを追加できます。

こちらがactionの具体例です↓

{type: 'USER_INPUT', value: 1} //typeは慣例でつける、valueは実装上必要なため設定する

dispatch関数でどうやってactionを作るのか?

dispatchFn({ type: 'USER_INPUT', value: 1 });

これでdispatch関数でactionが作れます。

このように記述することでdispatch関数からactionがreducer関数に渡されます。

reducer関数はどう記述するのか?

const reducerFn = (state, action) => {
  return {新しいstate};
};

第一引数に現在の状態、第二引数にdispatch関数から送られてきたactionを持ちます。

そして新しい状態をreturnで返します。

注意点はreducer関数はコンポーネントの外側に書きます。

これはreducer関数がコンポーネント内の関数と関与することがないのでわざわざコンポーネント内に書く必要がないためです。

コンポーネント内に記述しているdispatch関数とは関わりがあるように見えますが、このdispatch関数から出てくるactionはReactが自動的にreducer関数に送るのでここでは繋がりを考えなくて良いのです。

ここは少しややこしいので、reducer関数はコンポーネントの外側に記述する。と覚えておきましょう。

ここまでの流れを図を用いて復習しておきましょう。

useReducerの流れ
  • まず初期値を設定
  • 次にコンポーネント内にdispatch関数でアクションを作る
  • そのactionをreducer関数で受け取る(コンポーネント外に記述)
  • 現在の値であるstateと受け取ったactionを使ってゴニョゴニョして
  • 新しい値を作る

ここから実例を見ながら解説していきます。

今回はパスワードを7文字以上入力すると 入力欄の背景とボタンの色が変化する実装です。

こんな感じです。

実際のソースコードがこちらです。

理解しやすくするためにシンプルに書いています。

import React, { useReducer } from 'react';
import './ReactDev.css';

const reducerFn = (state, action) => {
  if (action.type === 'USER_INPUT') {
    return {
      value: action.value,
      isValid: action.value.trim().length > 6,
    };
  }
  return {
    value: '',
    isValid: false,
  };
};

const ReactDev = () => {
  const [passwordState, dispatchFn] = useReducer(reducerFn, {
    value: '',
    isValid: false,
  });

  const inputHandler = (e) => {
    dispatchFn({ type: 'USER_INPUT', value: e.target.value });
  };

  return (
    <div className="container">
      <form>
        <input
          className={`${passwordState.isValid === false ? 'invalid' : ''}`}
          type="password"
          value={passwordState.value}
          onChange={inputHandler}
        />
      </form>
      <div className={`${passwordState.isValid === false ? 'btn' : 'btn on'}`}>
        登録
      </div>
    </div>
  );
};

export default ReactDev;

ではコードの解説をしていきます。

①まずソースの中盤、コンポーネント何でuseReducer()を設定しています。

ここで初期値をオブジェクトで設定していますね。

const ReactDev = () => {
  const [passwordState, dispatchFn] = useReducer(reducerFn, {
    value: '',
    isValid: false,
  });

次にJSX内のinputタグにイベントを設定しています。

<input
  className={`${passwordState.isValid === false ? 'invalid' : ''}`}
  type="password"
  value={passwordState.value}
  onChange={inputHandler}  //入力のたび発火
/>

これらのイベントが発火するとdispatch関数が動くようになります。

const inputHandler = (e) => {
    dispatchFn({ type: 'USER_INPUT', value: e.target.value });
};

②このdispatch関数でactionを設定します。

③そして即座にコンポーネントの外側に記述したreducer関数にactionが渡されます。

const reducerFn = (state, action) => {
  if (action.type === 'USER_INPUT') {
    return {
      value: action.value,
      isValid: action.value.trim().length > 6,
    };
  }
  return {
    value: '',
    isValid: false,
  };
};

④そしてreducuer関数の中でゴニョゴニョして新しい値を返します。

今回の実装例ではaction.typeで条件をつけて、結果を出し分けています。

⑤下記の2つの新しい値が作られます。

  • value: action.value (入力されたフォームの値)
  • isValid: action.value.trim().length > 6(trueかfalse)

実装の例は以上となります。

全体の流れの図をもう一度お見せします。実装の解説を読みながら図で流れを確認すると理解が深まると思います。(図内の数字はコード解説のナンバリングと一致するようにしています。)

ここでこの記事の冒頭を思い出していただいきたのですが、「useReducerはコンポーネントで管理する状態(値)が2つ以上などより複雑な状態(値)の変更を管理する時にuseStateの代わりに使われます」と紹介しました。

では今回の実装で管理している状態は何になるでしょうか?

正解は「テキスト入力の状態」と「バリデーションの状態」の2つです。

具体的にはテキスト数をカウントして管理させているのとバリデーションがtrueかfalseかを管理しています。

useState()と実装の違い

ここでuseState()でも同じ実装をして違いを見てみましょう。

import React, { useState } from 'react';
import './ReactDev.css';

const ReactDev = () => {
  const [enteredValue, setEnteredValue] = useState('');
  const [paswordState, setPasswordState] = useState(false);

  const inputHandler = (e) => {
    setEnteredValue(e.target.value);
    setPasswordState(e.target.value.trim().length > 6);
  };

  return (
    <div className="container">
      <form>
        <input
          className={`${paswordState === false ? 'invalid' : ''}`}
          type="password"
          value={enteredValue}
          onInput={inputHandler}
        />
      </form>
      <div className={`${paswordState === false ? 'btn' : 'btn on'}`}>登録</div>
    </div>
  );
};

export default ReactDev;

今回の実装例だとuseState()の方がシンプルに記述できますね。

しかし、より複雑な状態を管理しなければならない仕様であればuseState()だとコードがぐちゃぐちゃになる可能性があります。

またuseReducer()を使うことのメリットとして初期値をオブジェクトを使用することができます。

これがより複雑なアプリの実装を簡単にしてくれます。

useState()でも初期値でオブジェクトを使用できるのですが、オブジェクトが複雑な場合、初期値の作成と更新が複雑になってしまいます。

最後に

今回はuseReducer()の解説をしました。

useReducer()とuseState()は使い分けで悩むところですが、判断基準はこちらを持っておきましょう。

  • 状態の更新が単純な実装ではuseState()で十分
  • 状態の更新がより複雑な実装ではuseReducer()を検討
  • 多くの実装ではuseState()で事足りるので、基本はuseState()を使いつつ、状態管理がややこしくなったら、useReducer()でスッキリ書けないかを考える

少しでもこれからプログラミングを学ぼうと考えている方の為になる発信をできれば思います。

私がReactを学んでいるUdemyの講座が下記です。

この私のReactの全記事も下記の講座をより理解しやすく解説したものです。講座を受けつつ、不明点をこのブログで解消できるような構成になっています。

48時間と長いですが基本から応用までかなり解説してくれているので興味があればぜひチェックしてみてください😌

ベストセラー取得

-React学習, React文法