useMemo 完全入門【React】計算結果をキャッシュしてパフォーマンス最適化

ReactのuseMemoとは何か、計算結果のキャッシュの仕組みと使いどころを初心者向けに解説。useCallbackとの違いや、使いすぎを避けるためのベストプラクティスも紹介します。

#react#hooks#usememo#javascript#frontend

Reactアプリのパフォーマンスを改善したいとき、最初に出会うフックの一つが useMemo です。

「重い計算処理が毎回のレンダリングで実行されてしまう」「リストのフィルタリングが遅い」——そんな悩みを解決するのが useMemo です。ただし使いどころを間違えると逆効果になることもあるので、正しく理解しておきましょう。

この記事でわかること:

  • useMemo が必要な場面と仕組み
  • 基本的な書き方とパラメータ
  • useCallback との違い
  • 使いすぎを避けるためのガイドライン

useMemo とは?

useMemo は、計算処理の結果をキャッシュ(メモ化)するフックです。依存配列の値が変わらない限り、前回の計算結果をそのまま使い回します。

const cachedValue = useMemo(calculateValue, dependencies);
パラメータ説明
calculateValueキャッシュしたい値を計算する関数(引数なし、純粋関数)
dependencies変化を監視する値の配列

動作の仕組み

初回レンダリング:calculateValue を実行 → 結果をキャッシュ

再レンダリング時:
  ① dependencies が前回と同じ → キャッシュした値をそのまま返す(計算スキップ)
  ② dependencies が変わった   → calculateValue を再実行 → キャッシュを更新

基本的な使い方

重い計算処理のキャッシュ

import { useMemo } from "react";
 
function ProductList({ products, filterText }) {
  // filterText が変わったときだけ再フィルタリング
  const filteredProducts = useMemo(
    () =>
      products.filter((product) =>
        product.name.toLowerCase().includes(filterText.toLowerCase())
      ),
    [products, filterText] // この値が変わったら再計算
  );
 
  return (
    <ul>
      {filteredProducts.map((product) => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>
  );
}

useMemo がなければ、filterText とは無関係な state が変わってもフィルタリング処理が毎回走ってしまいます。

memo コンポーネントとの組み合わせ

import { memo, useMemo, useState } from "react";
 
// memo でラップした子コンポーネント
const ItemList = memo(function ItemList({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
});
 
function Parent({ data, theme }) {
  // theme が変わっても items の参照が変わらない → ItemList の再レンダリングをスキップ
  const items = useMemo(
    () => data.filter((item) => item.active),
    [data]
  );
 
  return (
    <div className={theme}>
      <ItemList items={items} />
    </div>
  );
}

エフェクトの過剰実行を防ぐ

function SearchComponent({ query }) {
  // options オブジェクトをメモ化(毎回新しいオブジェクトが作られるのを防ぐ)
  const searchOptions = useMemo(
    () => ({ minLength: 3, caseSensitive: false }),
    [] // 依存なし:一度だけ作成
  );
 
  useEffect(() => {
    // searchOptions が毎回新しいオブジェクトだとエフェクトが毎回実行されてしまう
    searchApi(query, searchOptions);
  }, [query, searchOptions]);
}

useCallback との違い

よく混同される useCallback との違いを整理します。

フックキャッシュするもの内部的な実装
useMemo計算結果(値)useMemo(() => fn, deps)
useCallback関数そのものuseMemo(() => fn, deps) と同等
// useMemo:計算結果をキャッシュ
const filteredList = useMemo(
  () => list.filter(isActive),
  [list]
);
 
// useCallback:関数をキャッシュ
const handleClick = useCallback(
  () => console.log("クリック"),
  []
);

よくあるミス

ミス1:アロー関数の書き方

// ❌ NG:{} はオブジェクトではなくブロックとして解釈される
const data = useMemo(() => { name: "React" }, []);
// → undefined が返される
 
// ✅ OK:丸括弧でオブジェクトを囲む
const data = useMemo(() => ({ name: "React" }), []);

ミス2:依存配列を忘れる

// ❌ NG:依存配列がないので毎回再計算される
const result = useMemo(() => heavyCalculation(data));
 
// ✅ OK:依存配列を必ず指定する
const result = useMemo(() => heavyCalculation(data), [data]);

使いすぎに注意

useMemoすべての計算に使う必要はありません

// ❌ 過剰:単純な計算には不要
const doubled = useMemo(() => count * 2, [count]);
 
// ✅ シンプルに書けばよい
const doubled = count * 2;

useMemo 自体にもメモリ使用やオーバーヘッドのコストがあります。使う目安は:

  • 処理に明らかに時間がかかる(リストのフィルタリング・ソートなど)
  • memo でラップした子コンポーネントに渡すオブジェクト・配列
  • useEffect の依存配列に入れるオブジェクト

まず計測して、遅い場合にのみ追加するのがベストプラクティスです。

まとめ

  • useMemo は計算結果をキャッシュして、不要な再計算を防ぐフック
  • dependencies が変わらない限り、キャッシュされた値を返し続ける
  • useCallback は関数をキャッシュする(useMemo の関数版)
  • 重い計算・memo コンポーネントへの props・エフェクトの依存値に使う
  • パフォーマンス問題が実際に起きてから使うのが正しいアプローチ

PR

useMemo 公式ドキュメントで詳細を確認しよう

useMemoのインタラクティブなサンプルや、React Compilerとの関係など最新情報は公式ドキュメントで確認できます。

useMemo 公式ドキュメントを見る

関連記事