Zenn
📝

[React] useMemoはいつ使うべきなのか

2025/02/10に公開
2

はじめに

React で状態管理をする際に useState を使うことが多いですが、パフォーマンス最適化のために useMemo を使うべき場面もあります。しかし、setCount のような単純な状態変更を行う場合、useMemouseState の違いが分からないことが多いです。

例えば、以下のようなカウンターを作成するとします。

import { useState } from "react";

const Counter = () => {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>増やす</button>
    </div>
  );
};

この場合、useMemo を使う意味はほとんどありません。では、どのような場面で useMemo を使うべきなのでしょうか?

useMemo が有効な場面

useMemo の主な用途は「計算コストの高い処理を最適化すること」です。たとえば、以下のようなリストフィルタリングの処理があるとします。

useState のみを使用した場合(最適化なし)

import { useState } from "react";

const heavyCalculation = (items) => {
  console.log("重い計算を実行中...");
  return items.filter(item => item.includes("a"));
};

const Example = () => {
  const [query, setQuery] = useState("");
  const [items] = useState(["apple", "banana", "cherry", "grape", "orange"]);

  const filteredItems = heavyCalculation(items);

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <ul>
        {filteredItems.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
};

このコードでは query を変更するたびに heavyCalculation が実行されますが、query の変更は items とは関係がないため、毎回不要な再計算が発生しています。

useMemo を使用して最適化した場合

import { useState, useMemo } from "react";

const heavyCalculation = (items) => {
  console.log("重い計算を実行中...");
  return items.filter(item => item.includes("a"));
};

const Example = () => {
  const [query, setQuery] = useState("");
  const [items] = useState(["apple", "banana", "cherry", "grape", "orange"]);

  const filteredItems = useMemo(() => heavyCalculation(items), [items]);

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={(e) => setQuery(e.target.value)}
      />
      <ul>
        {filteredItems.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
};

このように useMemo を使うことで query を変更しても filteredItems の計算が不要になり、無駄な計算を防ぐことができます。

useStateuseMemo の適切な使い分け

useState useMemo
目的 UI の状態管理 計算コストの高い処理をキャッシュ
いつ使う? ユーザーの操作で変化するデータ 計算結果の再利用が必要な場合
トリガー setState を呼ぶたびに再レンダリング 依存配列 ([]) の値が変わると再計算
副作用 あり (setState による UI 更新) なし(計算結果のキャッシュのみ)
再レンダリング setState を呼ぶたびに発生 依存値が変わらない限り発生しない
使いどころ ボタンのクリック、フォーム入力 配列の map, filter の最適化、オブジェクトの安定化

useMemo を使うべき場面

  1. 計算コストが高い処理がある場合

    • 配列の map, filter, reduce などの処理が重い
    • オブジェクトの生成や JSON.parse(JSON.stringify(obj)) のような処理が頻繁に行われる
  2. 無関係な state の変更で不要な再計算が発生する場合

    • query のように、他の state の変更とは関係ない計算が何度も実行される
  3. レンダリング時に毎回オブジェクトや関数が新しく生成されるケース

    • useEffect の依存関係としてオブジェクトを渡す場合

3が少しわかりにくいので具体例を踏まえて説明します。
例えば、以下のコードでは、data というオブジェクトを useEffect の依存配列に渡しています。

import { useState, useEffect, useMemo } from "react";

const Example = () => {
  const [count, setCount] = useState(0);

  const data = { value: 42 };

  useEffect(() => {
    console.log("useEffect が実行されました");
  }, [data]);

  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={() => setCount((prev) => prev + 1)}>増やす</button>
    </div>
  );
};

data{ value: 42 }という同じオブジェクトを保持しているように見えますが、コンポーネントが再レンダリングされるたびに新しい{ value: 42 }が作られます。
それによって、毎回useEffectが実行されることになってしまいます。

解決策: useMemoを使う
この問題を解決するには、useMemoを使ってdataをキャッシュします。

import { useState, useEffect, useMemo } from "react";

const Example = () => {
  const [count, setCount] = useState(0);

  const data = useMemo(() => ({ value: 42 }), []); // 🎯 依存配列が空なので、最初のレンダリング時にのみ作成される

  useEffect(() => {
    console.log("useEffect が実行されました");
  }, [data]); // `data` が変わらない限り useEffect は再実行されない

  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={() => setCount((prev) => prev + 1)}>増やす</button>
    </div>
  );
};

これでdataのメモリアドレスが変わらなくなり、useEffectが毎回実行される問題を防げます🙌

useMemo を使わなくてもよい場面

  • 単純な状態管理のためだけに使う場合
    • useState のみで十分なケース(例: count の更新)
  • レンダリングコストが低い処理に対して使う場合
    • 固定値や小さい配列のレンダリング(map などの処理が少ない)
    • 単純な数値計算(四則演算や小規模な reduce)
    • 小規模なオブジェクトの生成(単純なキー・バリューのオブジェクト)
  • useCallback で管理すべき関数のキャッシュと混同する場合
    • 関数のキャッシュには useCallback を使うべき

まとめ

useStateUI の状態管理 に適しており、useMemo計算コストの高い処理を最適化する のに適しています。単純な count の更新では useMemo を使っても意味がありませんが、不要な再計算を防ぐことでパフォーマンスを向上させることができます。

💡 「本当に計算が重いのか?」を考えて useMemo を適用するのがベストプラクティス!

2
codeciaoテックブログ

Discussion

ログインするとコメントできます