Gemcook Tech Blog
🛡️

【React】useState派必見!useReducerでコードを守る

2024/08/06に公開

はじめに

こんにちは!
皆さん、useReducerは使っていますか?
「useStateでいいやん。」という声が聞こえてきます。はい、筆者自身もそうでした。Reactの公式も以下の記載があります。

個人の好み:人によってリデューサが好きだったり、好きではなかったりします。それで構いません。好みの問題です。useState と useReducer の間を行ったり来たりすることはいつでも可能です。どちらも同等のものです!

ですが、useReducerのコードに遭遇してから色々調べていくうちに、useReducerを使うことで堅牢なコードに繋がることを知りました。そこでの学びを記事にしましたので、見ていってください!!

https://ja.react.dev/reference/react/useReducer#writing-the-reducer-function

useReducerのGoodPoint👍

stateと更新関数を同時に宣言できる

useReducerは、stateと更新関数を同時に宣言できます。これがどのように役立つのかを、複数の入力値を管理するstateを例に、useStateを使う場合とuseReducerを使う場合を比較してみましょう。

import { useState } from "react";

// useStateを使用した場合
export const Index = () => {
  const [state, setState] = useState({ name: "", job: "" });
  return (
    <div>
      <div>{state.name}</div>
      <div>{state.job}</div>
      <input
        type="text"
        onChange={(e) => {
          const { value } = e.target;
          setState((prev) => ({ ...prev, name: value }));
        }}
      />
      <input
        type="text"
        onChange={(e) => {
          const { value } = e.target;
          setState((prev) => ({ ...prev, job: value }));
        }}
      />
    </div>
  );
};

import { useReducer } from "react";

// useReducerを使用した場合
const Index = () => {
  const [state, dispatch] = useReducer(
    (state, action) => ({ ...state, ...action }),
    { name: "", job: "" }
  );
  return (
    <div>
      <div>{state.name}</div>
      <div>{state.job}</div>
      <input
        type="text"
        onChange={(e) => {
          const { value } = e.target;
          dispatch({ name: value });
        }}
      />
      <input
        type="text"
        onChange={(e) => {
          const { value } = e.target;
          dispatch({ job: value });
        }}
      />
    </div>
  );
};

useStateとuseReducerのどちらのパターンもスプレッド構文でstateを更新します。ここで注目して欲しいのは、useStateの場合、setState()で「stateをスプレッド構文で更新する」ロジックを記述している点です。一方、useReducerの場合、更新関数内(dispatch())であらかじめ「stateをスプレッド構文で更新する」ことを宣言しています。つまり、useStateは実際にstateを更新するところで、どのように更新するかを明記する必要があり、useReducerはあらかじめstateをどのように更新するかを宣言しており、その更新関数を呼び出すだけになります。

もし、このロジックを実装した人が「スプレッド構文で部分的にオブジェクトを更新したい」という意図を持っていたとします。オブジェクトのプロパティを追加する開発が行われる際、この意図を知らずに開発すると、useStateでは以下のようにオブジェクト全体を更新することも可能であり、当初の設計の意図に反しバグに繋がってしまうかもしれません。対してuseReducerの場合、あらかじめstateをどのように更新するかを宣言しているため、より安全なコードになります。

setState({ name: state.job, job: state.job, age: value })

useReducerを使うことで「このstateはこうやって更新する」というロジックを明示的に宣言できるため、堅牢なコードが実現します。

一方向にstateを更新したいときに最適

例えば、初めは非表示で一度表示したらそのあとはずっと表示し続けるといったテキストがあるとします。そのように一度だけstateを更新したいときにuseReducerを使うと便利です。

import { useReducer } from "react";

const Index = () => {
  const [state, dispatch] = useReducer(() => true, false);

  return (
    <div>
      <button type="button" onClick={dispatch}>
        Show Text
      </button>
      {!!state && <p>一度表示したらずっと表示しっぱなし</p>}
    </div>
  );
};

初期状態はfalseであり、ボタンがクリックされると更新関数(dispatch())が呼ばれ、stateがtrueに更新されます。このように、useReducerを使うことで、一方向にstateを更新する場合のロジックが明確になり、意図しないstate更新(この場合はtrue→falseへの誤更新)を防ぐことができます。

最後に

useReducerとuseStateを比較したとき、useStateの方が可読性が高く、使いやすいと筆者自身も感じています。しかし、useReducerの方が適している場面もあることを知りました。特に堅牢なコードを書くことにつながるという点は、大きな収穫でした。

基本的にはuseStateを使うことで問題はありませんが、ふとしたときに「あれ?useReducerを使った方が保守的にもイケてるコードになるのでは?」と思っていただけるようになると幸いです。

useReducerを使って、より良いReactライフを楽しんでください!

Gemcook Tech Blog
Gemcook Tech Blog

Discussion