React再レンダリング最適化|memo・useMemo・useCallbackの正しい使い分け

「入力欄に文字を打つたびに画面全体が重くなる」──Reactアプリの性能問題として最もよく相談されるパターンです。原因の多くは、意図しない範囲まで再レンダリングが波及していることにあります。

本記事では、Reactの再レンダリングの仕組みを押さえたうえで、memo・useMemo・useCallbackの使い分け、リスト表示のkeyの扱い、そして「最適化のやり過ぎ」への注意点を整理します。

目次

再レンダリングはいつ起きるのか

まず前提の整理です。Reactコンポーネントが再レンダリングされるのは、基本的に次の場合です。

  • 自身のstateが更新されたとき
  • 親コンポーネントが再レンダリングされたとき(propsが変わっていなくても)
  • 購読しているContextの値が変わったとき

重要なのは2点目で、親が再レンダリングされれば子はデフォルトで全部再レンダリングされるという点です。「propsが変わったから再レンダリングされる」という理解は正確ではありません。また、再レンダリング=DOM更新ではなく、関数の再実行と差分計算です。それ自体は軽い処理なので、まず問題になっているかを計測で確認します。React DevToolsのProfilerで「どこが・なぜ・どれだけ」レンダリングされているかを見るのが第一歩です。

memo / useMemo / useCallback の使い分け

3つのAPIは役割が異なります。混同されがちなので整理します。

  • React.memo: コンポーネントを包み、propsが浅い比較で等しければ再レンダリングをスキップする
  • useMemo: 計算結果をメモ化する。重い計算の再実行防止と、参照の安定化に使う
  • useCallback: 関数の参照をメモ化する。useMemo(() => fn, deps) の糖衣構文

落とし穴は、memoで包んだ子にオブジェクトや関数をpropsとして渡すケースです。レンダリングのたびに新しい参照が生成されるため、浅い比較が常に不一致となりmemoが無効化されます。

const Child = React.memo(ChildComponent);

function Parent() {
  const [count, setCount] = useState(0);
  // NG: 毎回新しい関数が生成され、Childのmemoが効かない
  // const handleClick = () => { ... };

  // OK: 参照が安定し、memoが機能する
  const handleClick = useCallback(() => { /* ... */ }, []);
  return <Child onClick={handleClick} />;
}

つまりuseCallback/useMemoによる参照の安定化は、受け手側のmemo(またはuseEffect等の依存配列)とセットで初めて意味を持ちます。単体で付けても速くはなりません。

リスト表示のkeyを正しく使う

リストのkeyは、Reactが「どの要素が同一か」を判断するための識別子です。ここを誤ると性能とバグの両方に響きます。

  • keyには要素固有の安定したID(DBのidなど)を使う
  • 配列のindexをkeyにすると、並び替え・挿入・削除時に要素の対応付けがずれ、不要な再レンダリングや入力状態の混線が起きる
  • Math.random() など毎回変わる値は論外で、全要素が毎回作り直されます

「表示順が変わらない静的なリスト」であればindexでも実害は出にくいですが、後から並び替え機能が追加されて壊れるのが定番なので、最初からIDを使う習慣にしておくのが安全です。

やり過ぎ注意|最適化より先に構造を見直す

すべてをmemoで包み、あらゆる関数をuseCallbackで囲うのはアンチパターンです。メモ化自体にも比較コストとメモリコストがあり、依存配列の管理ミスは「古い値を参照し続ける」厄介なバグを生みます。何よりコードの可読性が下がります。

メモ化の前に、構造で解決できないかを検討します。

  • stateを下げる: 入力値のstateをフォーム部分の小さなコンポーネントに閉じ込め、ページ全体の再レンダリングを避ける
  • childrenで渡す: 重いサブツリーを親のpropsとして受け取れば、親のstate更新の影響を受けない
  • Contextは更新頻度で分割する(テーマ等の静的な値と、頻繁に変わる値を同居させない)

なお、React 19以降で導入が進むReact Compilerはメモ化を自動化する方向にあり、手動メモ化の役割は今後小さくなっていく見込みです。だからこそ「構造で解決する」設計の基礎が長く効きます。

まとめ

再レンダリング最適化の要点は、「親の再レンダリングは子に波及する」という仕組みの理解、memoと参照安定化(useMemo/useCallback)をセットで使うこと、リストのkeyに安定したIDを使うこと、そして計測せずにメモ化をばら撒かないことです。Profilerで実害のある箇所を特定し、まずstateの配置とコンポーネント構造で解決を試み、それでも残る部分にだけメモ化を適用する。この順序を守るだけで、コードの複雑さを抑えつつ体感性能を改善できます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

クラウド・バックエンドエンジニア。AWSを中心に設計・構築から運用までを担当しています。主要言語は Java・JavaScript・Python。運用の現場で拾った知見を、再現できる手順に落として残すのがこのブログのテーマです。

目次