⚛️ React Boundary
内部理解 — パフォーマンス

React.memo / useMemo / useCallback の本質

「とりあえずuseMemoを使えばいい」は間違い。メモ化の仕組みを理解することで、いつ・なぜ使うべきかがわかります。

🎯 3つの違いを整理する

React.memo

コンポーネントのメモ化

propsが変わっていなければ、子コンポーネントの再レンダリングをスキップする。

useMemo

計算結果のメモ化

依存配列が変わっていなければ、前回の計算結果を再利用する。

useCallback

関数のメモ化

依存配列が変わっていなければ、前回と同じ関数参照を返す。

🔑 前提知識:なぜメモ化が必要になるのか

Reactでは、親コンポーネントが再レンダリングされると、すべての子コンポーネントも再レンダリングされます(デフォルト)。 これは子のpropsが変わっていなくても同様です。

function Parent() {
  const [count, setCount] = useState(0);
  
  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>+</button>
      {/* ↓ countが変わるたびに再レンダリングされる */}
      <ExpensiveChild data={staticData} />
    </div>
  );
}

ExpensiveChildのprops(staticData)は変わっていないのに、 毎回再レンダリングされます。これを防ぐのがReact.memoです。

⚛️ React.memo:コンポーネントのメモ化

// React.memoでラップ
const ExpensiveChild = React.memo(function({ data }) {
  // dataが変わった時だけ再実行される
  return <div>{/* 重い処理 */}</div>;
});

⚠️ 落とし穴:関数をpropsに渡す場合

function Parent() {
  const [count, setCount] = useState(0);
  
  // 毎回新しい関数が作られる!
  const handleClick = () => console.log('clicked');
  
  return <MemoizedChild onClick={handleClick} />;
  // ↑ handleClickの参照が毎回変わる → Memoの意味なし
}

これを解決するためにuseCallbackが必要になります。

🔧 useCallback:関数参照を安定させる

function Parent() {
  const [count, setCount] = useState(0);
  
  // ✅ 依存配列が変わらない限り、同じ関数参照を返す
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []); // 依存なし → 毎回同じ参照
  
  return <MemoizedChild onClick={handleClick} />;
  // ✅ handleClickの参照が変わらない → Memoが有効
}

useCallback は useMemo の関数版

// この2つは等価
useCallback(fn, deps);
useMemo(() => fn, deps);

💡 useMemo:重い計算をキャッシュする

function Component({ items, filter }) {
  // ❌ 毎回のレンダリングで重い計算が走る
  // const filtered = expensiveFilter(items, filter);
  
  // ✅ filterまたはitemsが変わった時だけ再計算
  const filtered = useMemo(
    () => expensiveFilter(items, filter),
    [items, filter]
  );
  
  return <List items={filtered} />;
}

useMemoを使うべきタイミング

  • 計算コストが高い処理(フィルタ・ソート・集計など)
  • 下流のReact.memoコンポーネントに渡すオブジェクト・配列(参照の安定化)
  • useEffectの依存配列に入れるオブジェクト

⚡ メモ化はただじゃない

メモ化にはコストがあります。「すべてにuseMemoを使えばいい」は間違いです。

メモ化のコスト

メモリ 前回の値・関数・計算結果をメモリに保持し続ける
比較コスト 毎回依存配列を Object.is で比較するオーバーヘッド
複雑さ コードの可読性が下がり、バグを生みやすくなる

使うべきでない場合

  • 計算が単純(数値の足し算など)な場合
  • コンポーネントが頻繁に再レンダリングされない場合
  • 依存配列の値が毎回変わる場合(メモ化の恩恵なし)

📌 まとめ

  • ✓ React.memo:propsが変わらなければ子コンポーネントの再レンダリングをスキップ
  • ✓ useMemo:重い計算結果をキャッシュ(依存配列が変わった時だけ再計算)
  • ✓ useCallback:関数参照を安定させる(React.memoと組み合わせて使う)
  • ✓ 関数・オブジェクトは毎回新しい参照が生まれるため、参照の安定化が重要
  • ✓ メモ化には比較コスト・メモリコストがある——必要な場所だけ使う

関連記事