T-CREATOR

Reactの再レンダリングを制御する!memo・useMemo・useCallbackの使い分けを紹介

Reactの再レンダリングを制御する!memo・useMemo・useCallbackの使い分けを紹介

Reactのパフォーマンス最適化における鍵を握るのが、不要な再レンダリングの抑制です。

アプリケーションが大規模化するにつれ、コンポーネントの描画コストは無視できないものになります。Reactにはこの再レンダリングを制御するための便利なフックやコンポーネントラッパーが用意されており、特に重要なのが React.memouseMemouseCallback です。

本記事では、これら3つの違いや適切な使い分け、注意点を初心者の方でも理解できるように、実例を交えながら丁寧に解説いたします。


コンポーネントの再レンダリングとは

Reactでは、状態やプロパティが変更されると該当コンポーネントが再レンダリングされます。

再レンダリングとは、仮想DOMの差分比較(Reconciliation)を行い、実際のDOMに変更を反映する処理です。

この一連の処理は小規模であれば問題になりませんが、以下のようなケースではパフォーマンスが低下します:

  • 再レンダリング時に重い処理が走る(例:ソートやフィルター)
  • 子コンポーネントが無関係な変更でも再描画される
  • useEffectなどの副作用が無駄に実行される

こうした無駄な再レンダリングを防ぐ手段として登場するのが、memouseMemouseCallbackです。


React.memo:コンポーネント単位のメモ化

最も基本的かつ強力な最適化手法が React.memo です。

概要

React.memo純粋関数コンポーネントをメモ化する高階コンポーネント(HOC)です。

同じ props であれば、再レンダリングをスキップしてくれます。

公式ドキュメント

React.memo - React公式

基本使用例

tsximport React from 'react';

const Button = React.memo(({ onClick, children }: { onClick: () => void; children: React.ReactNode }) => {
  console.log('Button rendered');
  return <button onClick={onClick}>{children}</button>;
});

解説

この Button コンポーネントは React.memo によってラップされているため、props が変わらない限り再レンダリングされません

以下のような親コンポーネントがあったとします:

tsxexport const Parent = () => {
  const [count, setCount] = React.useState(0);

  const handleClick = () => {
    console.log('Clicked');
  };

  return (
    <>
      <p>Count: {count}</p>
      <Button onClick={handleClick}>Click me</Button>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </>
  );
};

このとき、handleClick が新しい参照になるため、Button は再レンダリングされます。これを防ぐには後述の useCallback が必要です。


useCallback:関数の参照を固定する

useCallback は関数をメモ化し、不必要な再生成を防ぐフックです。

公式ドキュメント

Hooks API Reference - useCallback

使用例

tsxconst handleClick = useCallback(() => {
  console.log('Clicked');
}, []);

解説

第二引数の依存配列が変わらない限り、handleClick は常に同じ関数の参照を返します。

memouseCallback を組み合わせると、無駄な再レンダリングを完全に防ぐことができます。


useMemo:値のメモ化

useMemo計算結果をメモ化するためのフックです。

レンダリングごとに実行される処理を避け、前回の値を再利用することでコストを削減します。

公式ドキュメント

Hooks API Reference - useMemo

基本例

tsxconst sortedList = useMemo(() => {
  return list.sort((a, b) => a.localeCompare(b));
}, [list]);

注意点

  • 関数の副作用があるものには使わない(useEffectに分離すべき)
  • 計算が軽量な場合は不要

使い分けの考え方と整理表

以下のように整理するとわかりやすくなります:

機能メモ化対象再評価条件主な用途
React.memoコンポーネントpropsが変わる時コンポーネント再レンダリング防止
useCallback関数依存配列が変わる時関数の再生成防止(memoと併用)
useMemo計算値依存配列が変わる時重い計算結果のキャッシュ

よくあるパフォーマンス改善パターン

フィルターや検索の処理

tsxconst filteredItems = useMemo(() => {
  return items.filter((item) => item.includes(keyword));
}, [items, keyword]);

子コンポーネントのイベントハンドラー

tsxconst handleLike = useCallback(() => {
  onLike(itemId);
}, [itemId, onLike]);

過剰な最適化に注意

以下のようなケースでは逆にパフォーマンスが悪化することもあります:

  • useMemo による不要なキャッシュ
  • useCallback による不要な関数ラップ
  • React.memo による比較コストが高すぎる(deep equalityなど)

「本当にコストが高いか?」を計測した上で適用することが重要です。


開発者ツールでの確認

React DevTools を使えば、コンポーネントの再レンダリング状況を視覚的に確認できます。

React Developer Tools

コンポーネントを選択し、Render CountProps の変更履歴を確認すると、適切にメモ化されているかが一目で分かります。


総まとめ

本記事では、Reactアプリケーションの再レンダリングを制御するために不可欠な3つの仕組みをご紹介しました。

  • React.memo:コンポーネント単位で再レンダリングを防ぐ
  • useCallback:関数の再生成を防ぐ
  • useMemo:計算値の再評価を防ぐ

適切な使い分けを実践することで、よりレスポンスの良いUIを構築することが可能になります。

ただし、過剰な最適化は逆効果となる可能性もあるため、「必要な場所だけに使う」意識が大切です。

最後までご覧いただきありがとうございました。

記事Article

もっと見る