ReactのuseStateとuseReducerを比較する。

2023/04/07に公開

はじめに

React の状態管理の Hook を比較して、それぞれの用途をまとめる。

カウンター

useState

useState を用いたカウンターの実装を以下に示す。

import { useState } from "react";

function Counter1() {
  const [count, setCount1] = useState(0);
  const increment = (payload) => {
    setCount1((prevCount) => prevCount + payload);
  };
  return (
    <>
      <h1>Count: {count}</h1>
      <button onClick={() => increment(1)}>+</button>
      <button onClick={() => increment(-1)}>-</button>
    </>
  );
}

useReducer

useReducer を用いたカウンターの実装を以下に示す。

import { useReducer } from "react";

function Counter2() {
  const [count, dispatch] = useReducer((state, action) => state + action, 0);
  return (
    <>
      <h1>Count: {count}</h1>
      <button onClick={() => dispatch(1)}>+</button>
      <button onClick={() => dispatch(-1)}>-</button>
    </>
  );
}

比較

useState の場合、状態の更新関数を別に定義する。setCount を更新関数を入れて作る等の工夫をして気をつけて実装しないといけない。useReducer の場合、状態の更新関数を同時に定義できるため、記述量が圧倒的に少なく出来る。

2 つのカウンター

useState

useState で2つのカウンター作成。

function DoubleCounter1() {
  const [count1, setCount1] = useState(0);
  const [count2, setCount2] = useState(0);

  const increment1 = (payload) => {
    setCount1((prevCount) => prevCount + payload);
  };
  const increment2 = (payload) => {
    setCount2((prevCount) => prevCount + payload);
  };

  return (
    <>
      <h1>Count1: {count1}</h1>
      <button onClick={() => increment1(1)}>+</button>
      <button onClick={() => increment1(-1)}>-</button>
      <h1>Count2: {count2}</h1>
      <button onClick={() => increment2(1)}>+</button>
      <button onClick={() => increment2(-1)}>-</button>
    </>
  );
}

useReducer

useReducer で2つのカウンター作成。

function DoubleCounter2() {
  const [state, dispatch] = useReducer(
    (state, action) => {
      switch (action.type) {
        case "increment1":
          return { ...state, count1: state.count1 + action.payload };
        case "increment2":
          return { ...state, count2: state.count2 + action.payload };
        default:
          return state;
      }
    },
    { count1: 0, count2: 0 }
  );

  return (
    <>
      <h1>Count1: {state.count1}</h1>
      <button onClick={() => dispatch({ type: "increment1", payload: 1 })}>
        +
      </button>
      <button onClick={() => dispatch({ type: "increment1", payload: -1 })}>
        -
      </button>
      <h1>Count2: {state.count2}</h1>
      <button onClick={() => dispatch({ type: "increment2", payload: 1 })}>
        +
      </button>
      <button onClick={() => dispatch({ type: "increment2", payload: -1 })}>
        -
      </button>
    </>
  );
}

比較

useState の場合、それぞれの状態を個別に定義する。useReducer の場合コンポーネント内の全ての状態を一箇所で定義し更新関数も定義している。
この場合はそれぞれのボタンが分離しているので、コードの記述量が短い useState の方が楽に見える。ただ、コンポーネント内の状態が複雑に絡み合うような場合においては useReducer の方が向いてるかもしれない。そもそも状態が完全に分離しているならコンポーネントも分けて実装すべきなので、実用上前者は起こりにくいと思う。

おわりに

React の useState と useReducer を用いてカウンターを実装した。useState の場合、静的にセットするか更新関数にするかを選んで実装しないといけないかわりに、コードの記述量が少ないというメリットがある。一方で、useReducer の場合、そもそもが更新関数として定義するので実装上考えることは少ないが、その分記述量が多いというデメリットがある。実用上は useReducer の方が書きやすくはあると思った。

Discussion