対象読者
Reactのメモ化は知っているけど、他のパフォーマンス改善方法も学びたい方
はじめに
Reactのパフォーマンス改善について調べると、useMemoやuseCallback といったメモ化テクニックがよく紹介されます。しかし、メモ化は最終手段のケースがあります。
本記事は、こちらの動画(How to Prevent Unnecessary Re-Renders in React)の内容紹介 + αとなります。実践的なパフォーマンス改善手法を紹介します。
元動画はReact公式ドキュメントよりも丁寧な解説で、すぐに効果が出る内容なので強くおすすめします!
問題の本質:不要な再レンダーはどこから来るのか?
問題のパターン
親コンポーネントのスタイル更新時に、子コンポーネントが毎秒数十回も再レンダーされるケース (mousemove 発火など)があります。
function ParentComponent() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const [childData, setChildData] = useState('...');
const handleMouseMove = (e) => {
// マウス移動のたびにstateを更新
setPosition({ x: e.clientX, y: e.clientY });
};
return (
<div onMouseMove={handleMouseMove}>
<div style={{ transform: `translate(${position.x}px, ${position.y}px)` }}>
追従する要素
</div>
{/* 激重コンポーネント */}
{/* マウス移動のたびに不要な再レンダー発生! */}
<VeryHeavy data={childData} />
</div>
);
}
この場合、positionの変更に関係ないVeryHeavyまで再レンダーされてしまいます。
解決策1:親をラッパー化し、親子のstateを分離
stateで管理する場合は、state管理の責務を分離します。
Before(悪い例)
import { useState } from "react"
import "./App.css"
function App() {
// `setCount` するたびに `App` 全体が再描画
const [count, setCount] = useState(0)
return (
<>
<div onClick={() => setCount((count) => count + 1)}>
<div> count is {count} </div>
<VeryHeavy>
描画がとても遅い、激重コンポーネントだと思ってください
</VeryHeavy>
</div>
</>
)
}
After(良い例)
import { useState } from "react"
import "./App.css"
function App() {
const Counter = ({ children }) => {
// 依存 state を利用コンポーネント内に閉じ込める
const [count, setCount] = useState(0)
return (
<div onClick={() => setCount((count) => count + 1)}>
<div> count is {count} </div>
{children}
</div>
)
}
// VeryHeavy の state が変わらない場合は再描画されない
return (
<>
<Counter>
<VeryHeavy>
描画がとても遅い、激重コンポーネントだと思ってください
</VeryHeavy>
</Counter>
</>
)
}
ポイント
- 子コンポーネントは自身のstate変更時のみ再レンダー
- 親のstateと子のstateが完全に分離
- 依存していないstateがコンポーネントに混在していないかチェック
解決策2: 高頻度イベントの最適化
mousemove、scroll などの高頻度イベントには、requestAnimationFrame と throttle を組み合わせることで負荷を削減できます。
2つの最適化ポイント
| 項目 | requestAnimationFrame | throttle |
|---|---|---|
| 実行タイミング | ブラウザの描画サイクルに同期 高負荷時は自動で頻度を落としてくれる 高リフレッシュレートのディスプレイでは自動で描画が増え、より滑らかに |
実行間隔の指定。そもそもの実行を減らす |
| バックグラウンド時 | 自動的に実行を抑制 | 通常通り実行 |
実装例
function ParentComponent() {
const [position, setPosition] = useState({ x: 0, y: 0 });
const rafIdRef = useRef(null);
const handleMouseMove = throttle((e) => { // lodash の throttle 等
if (rafIdRef.current) {
cancelAnimationFrame(rafIdRef.current);
}
rafIdRef.current = requestAnimationFrame(() => {
setPosition({ x: e.clientX, y: e.clientY });
});
}, 32); // 約30fps(60fpsの半分)
return <div onMouseMove={handleMouseMove}>{/* ... */}</div>;
}
パフォーマンス改善の優先順位
-
Local stateをちゃんとlocalに保つ
- 依存していないstateがコンポーネントにないか確認
- 必要に応じてコンポーネントを分割
-
高頻度イベントの最適化
-
requestAnimationFrameやthrottleを活用 - イベントの発火頻度を制御
-
-
上記で解決できない場合メモ化
-
useMemo,useCallback
-
再レンダーを可視化する最強ツール:React Scan
パフォーマンス問題を特定するには、どこで不要な再レンダーが起きているかを可視化することが重要です。
React Scan
- Chrome 拡張ツール が手軽だったのでこちらを使いました。
- 動画
冒頭で紹介した How to Prevent Unnecessary Re-Renders in React でもおすすめされており、実際に使ってみるとどのコンポーネントが再レンダーされているかが一目瞭然です!
結果
これらのテクニックを使った結果:
- 不要な子コンポーネントの再レンダーがゼロに
- ブラウザの状態に合わせて処理回数を自動で最適化
パフォーマンスが改善しました。
まとめ
- メモ化が改善に寄与しないケースもある。まずはstate管理の設計を見直す
- 依存していないstateがコンポーネントに混在していないか常に意識
- 高頻度イベントには
requestAnimationFrameやthrottleを活用 - React Scanで再レンダーを可視化して問題を特定
現場で即効果が出るのでぜひ試してみてください!
余談
元動画 のコメント欄にある投稿者Daniel Noworyataさんの返信が素敵です。技術への優しい眼差しを感じます。

最善策:Reactを使わないこと。
それでも最近はReactに関する悪口が多すぎると思うよ :)それでもプロジェクトで使う価値はある。
