Reactアプリケーションを開発していると、必ずぶつかるのが「パフォーマンスの問題」です。 特にSPA(Single Page Application)として複雑な要件を実装していくと、ちょっとした文字入力やボタンクリックで画面全体が再描画(レンダリング)され、ブラウザの動作がもたつくことがあります。
本記事では、2026年現在のReact開発において、無駄なレンダリングをいかに防ぎ、極限まで軽くヌルヌル動くUIを作るかという実践的なパフォーマンスチューニングの手法を徹底解説します。

1. そもそも「再レンダリング」とは?
Reactにおけるレンダリングは、画面の設計図(Virtual DOM)を作り直す作業です。 再レンダリング自体は悪ではありません。状態(State)が変われば、それに合わせて画面を更新するのはReactの基本原則です。
問題になるのは、**「まったく関係のないコンポーネントまでもが、連鎖的に再レンダリングされてしまう(不要な再レンダリング)」**ことです。
再レンダリングがトリガーされる3つの条件
- 自身の
stateが更新されたとき - 親コンポーネントが再レンダリングされたとき
- Context の値が更新されたとき
最も起きやすいのが「2. 親が変わったから子も全員再計算される」という現象です。これを防ぐのがパフォーマンス改善の第一歩です。
2. メモ化(Memoization)の基礎と落とし穴
React開発者がすぐに思いつく解決策が「メモ化」です。しかし、これを盲目的に使うと逆効果になることもあります。
React.memo: コンポーネント単位の防波堤
React.memo でコンポーネントを囲むと、「親が再レンダリングされても、渡されている Props が変化していなければこのコンポーネントは再レンダリングしない」という設定になります。
import { memo } from 'react';
// Propsが変わらない限り、このコンポーネントは再描画をスキップする
const HeavyChildComponent = memo(({ title, count }) => {
console.log("HeavyChildComponent rendered");
// ...重い処理や複雑なDOM...
return <div>{title}: {count}</div>;
});
厄介な「オブジェクトの参照」問題
ここで初心者が陥りやすい罠があります。
親コンポーネントから関数やオブジェクトを Props として渡す場合、親がレンダリングされる度に新しいメモリの番地にその関数やオブジェクトが作られ直すため、React.memo は「Propsが変更された!」と勘違いして再レンダリングを防げません。
// ❌ 悪い例:レンダリング毎に新しい関数が生成されるため、memoが効かない
function Parent() {
const [text, setText] = useState("");
// 親のtextが変わる度に、onClick関数が新しく作り直される
const handleClick = () => console.log("clicked");
return <ClickCounter onClick={handleClick} />;
}
useCallbackとuseMemoの出番
親の再レンダリング時にオブジェクトや関数が「新しく作り直される」のを防ぐために使うのがこれらです。
useCallback: 関数の生成を記憶するuseMemo: 計算結果やオブジェクトの生成を記憶する
// ⭕️ 良い例:useCallbackで関数を固定化
function Parent() {
const [text, setText] = useState("");
// 依存配列(第二引数)が空なので、一度作られた関数を使い回す
const handleClick = useCallback(() => console.log("clicked"), []);
return <ClickCounter onClick={handleClick} />;
}
3. Zustand / Jotai によるグローバルステートの局所化
Context API を使ってグローバルな状態管理を行うと、前述の「3. Contextの値が変わったら、そのContextを参照している全コンポーネントが再レンダリングされる」という仕様により、パフォーマンス劣化の温床になります。
近年では、Zustand や Jotai のような、必要な値だけを購読(Subscribe)できる軽量な状態管理ライブラリが主流です。
// Zustandでの例: stateの中から必要な `bears` だけを抽出して監視する
const bears = useStore((state) => state.bears);
このように書けば、store内の nuts という別の値が変わっても、bears だけを見ているコンポーネントは再レンダリングされません。
4. コンポーネントの分割と "State Colocation"
一番強力かつ、基本に忠実なパフォーマンス改善策が 「状態(State)を、本当にそれを必要とするコンポーネントの内部まで下げること(State Colocation)」 です。
画面のトップレベルにあたる <App /> にすべての State を持たせてしまうと、何か一つの入力欄の文字が変わるだけでページ全体が再計算されます。
「この State は誰が使うのか?」を考え、極力、ツリーの末端(子コンポーネント側)に State の宣言を移動させましょう。
5. まとめ
- すべてのものに
useMemoを付けるのはメモリの無駄遣い。本当に重い処理や、React.memoを付与した子へ渡す Props に限定する。 - グローバルな状態管理は、Contextではなく Zustand / Jotai などを使って再レンダリングの範囲を絞る。
- そもそも State の位置が高すぎないか見直し、不要に広い範囲を巻き込まないようにする(State Colocation)。
React Compiler の登場により自動でメモ化が行われる時代にもなってきていますが、これらの「Reactがどう動いているか」という基礎知識は、複雑なバグの調査やアーキテクチャ設計において確実にあなたの武器になります。ぜひ意識してコードを書いてみてください。