Reactを使っているとき、意図しない再描画(re-render)が起きてパフォーマンスが低下することがあります。なぜ再描画が起こるのか、どの状況で発生するのかを理解すれば無駄な再描画を減らし、アプリの反応速度や描画効率を大きく改善できます。この記事ではReact re-render 条件に焦点を当て、再描画の原因と条件、最新情報、最適化の方法をわかりやすく解説します。
目次
React re-render 条件とは何か
Reactにおける再描画(re-render)とは、コンポーネントが再び関数を実行し、JSXを返して仮想DOMとの差分を比較して必要なDOM更新を行う一連の処理を指します。この過程が起こる条件を正確に理解することは、無駄を減らす第一歩です。たとえば、親コンポーネントが re-render すると子コンポーネントも再描画されるかもしれませんが、その props や state が実際に変化していなければ DOM の更新は行われないこともあります。
以下に、React re-render 条件として最新の知見に基づいた主要なトリガーを整理します。
状態(state)の更新時
コンポーネント自身で useState の setter を呼び出したり、useReducer の dispatch が発生したりすると、そのコンポーネントおよびその子孫が再描画対象になります。state の更新があれば React は再描画をスケジュールします。
ただし、新しい state が前の state と浅く等しい(shallow equality)である場合、厳密には state は変わっていないためどの程度再描画が抑制されるかは定義されているメカニズムに依存します。
親コンポーネントの再描画
親コンポーネントが re-render すると、その内部で宣言されている子コンポーネントも関数が呼ばれます。props が変わらなければ DOM は更新されないこともありますが、関数コンポーネントの場合全て再評価されるため、子コンポーネント内部の処理次第ではコストがかかることがあります。
コンテキスト(Context)の値変更
コンポーネントが useContext を使ってコンテキストを受け取っている場合、そのコンテキストプロバイダーの値が変わると消費しているすべてのコンポーネントが再描画されます。コンテキストの更新は便利ですが、利用範囲を限定しなければ広範囲に再描画を引き起こしがちです。
強制再描画(forceUpdate)の呼び出し
クラスコンポーネントでは forceUpdate を使うことで shouldComponentUpdate を無視して再描画を強制できます。通常は不要な手段ですが、外部ストアの更新など React の state や props に依存しない更新を反映させたいときに使われます。
初回マウント時
コンポーネントが DOM に最初に描画されるとき(マウント時)も再描画の一種とみなされます。このタイミングではすべての子孫も描画されるため、初期レンダーの構造がパフォーマンスに与える影響は無視できません。初回レンダーのコストを軽くするため、構成を見直すことも重要です。
意図しない再描画が発生する典型的なケース
何も変わっていないのに再描画が発生すると、ユーザー体験が悪化しパフォーマンスにも影響します。ここではよくある条件・コードパターンから、意図しない再描画の例を具体的に挙げます。これらを理解することで、自分のコードで同じ問題を起こさないよう注意できるようになります。
Inline 関数やオブジェクトの生成
JSX 内や関数コンポーネント内部で直接定義された関数やオブジェクトは毎回新しい参照になります。React.memo を使っていても props の浅比較で異なると判断されるため、子コンポーネントが再描画されます。useCallback や useMemo を適切に使って関数やオブジェクトの参照を安定させる必要があります。
キー (key) の不安定な利用
配列をレンダリングする際、key に配列の index や乱数を使うとリストアイテムがマウント/アンマウントを繰り返し、再描画が多くなります。key はデータ固有の一貫した識別子にすることが望ましいです。そうすることで React はアイテムを正しく追跡し、必要最低限の更新にとどめられます。
State の場所(State Colocation)が不適切
state をツリーの上流に置きすぎると、その state の更新が親から子、孫へと再描画を波及させます。更新が限定的なコンポーネントに state を近づける(state colocation)ことで、不要な再描画を防げます。特に入力フォームやフィルターなど、再描画頻度が高い部分で有効です。
Context の過剰使用
一つの Context に state と dispatch を混ぜたりすると、Context の値が更新された際に消費するすべてのコンポーネントが再描画されます。必要であれば Context を分割するか、セレクタ方式の仕組みを導入することで、更新範囲を限定することができます。
Props の頻繁な再生成
親コンポーネントが毎回新しい props を子コンポーネントに渡していると、たとえ中身が同じでも参照が違えば再描画されます。関数・オブジェクト・配列などの props は再生成を避け、useMemo 等で安定化させるか、子コンポーネント側で memo を活用して浅比較を行うことがポイントになります。
React の最新情報に基づく再描画制御の技術
React のリリースやドキュメントには再描画制御に関する最新の最適化技術が多数登場しています。ここでは最新の考え方と API を交えて、意図しない再描画を抑える具体的な方法を紹介します。パフォーマンス改善を目指すならこれらは押さえておくべきです。
React.memo と arePropsEqual による比較
React.memo を用いることで、親から渡された props が前回と浅く等しい場合、子コンポーネントの再描画をスキップできます。さらに arePropsEqual という比較関数を渡せば、より柔軟に比較条件を指定でき、参照型や複雑な props でも制御しやすくなります。
useCallback と useMemo の組み合わせ
関数や配列・オブジェクトを props として渡す場合、その参照が毎回変わると再描画の原因になります。useCallback を使って関数の参照を安定させ、useMemo を使ってオブジェクトや値の計算結果をキャッシュし、props としての再生成を防ぐことが重要です。
Profiler と DevTools を使った可視化
React DevTools の Profiler を使えば、どのコンポーネントがどのくらい再描画しているか・どの時点でパフォーマンスが落ちているかを視覚的に把握できます。まず問題を「測定」し、それから対策を取る、という順序が効率的です。
状態のリフトアップと colocation の調整
state を必要以上に上流に配置すると、多くのコンポーネントが余計に再描画されます。反対に、state を使うコンポーネントの近くに配置することで、更新の影響を限定できます。入力フォームの state やフィルターの state は、可能な限りそれを使う子要素近くに置くことが推奨されます。
キーと配列操作の最適化
リストをレンダリングする際の key の選び方を見直すことが再描画抑制に効果的です。安定した一意な ID を使用し、index を避け、配列の並びが変わる場合は適切なキーを使うことが重要です。乱数やインデックスをキーに使うとアイテムの identity が毎回変化したと見なされ、無駄な再生成や再描画を引き起こします。
影響が大きい再描画パターンの比較
特定のコードパターンでどれだけ再描画コストが変わるかを比較することで、最適化の優先順位を判断できます。以下の表で典型的なパターンとそれに伴う再描画発生条件、そしてどのように改善できるかをまとめます。
| パターン | 再描画の条件 | 最適化方法 |
|---|---|---|
| 親コンポーネントの state 変更で子全体が re-render | 親の state 更新が原因 | state を近接させる/子を memo 化する |
| Inline 関数やオブジェクトを props に渡す | 毎回異なる参照を渡している | useCallback や useMemo で安定化 |
| Context 更新で広範囲が re-render | Context の値が変更されると consume 全部 | Context の分割やセレクタ方式を利用 |
| リストの key が不安定 | index や乱数を key に使用 | 安定した固有 ID を使う |
Class コンポーネントでの再描画制御
機能コンポーネントだけでなく、Class コンポーネントにも再描画の制御手段が存在します。特に既存プロジェクトやレガシーコードでクラス構文を使っている場合、これらを活用すると意外と効果が高いです。
shouldComponentUpdate の活用
クラスコンポーネントで再描画を防ぎたい場合、shouldComponentUpdate を定義して props と state の変化を比較し、不要な再描画を抑制できます。ただし浅い比較か深い比較かでパフォーマンスに差が出るため、処理コストと比較して採用を検討してください。
PureComponent の利用</
PureComponent は props と state を浅く比較する shouldComponentUpdate を既に備えているクラス基底クラスです。条件が単純な場合には PureComponent を使う方がコードが簡潔になります。ただし、複雑なオブジェクトやネストが深い props には注意が必要です。
forceUpdate のリスクと使い所
forceUpdate を使うと React の再レンダリングサイクルを強制できますが、shouldComponentUpdate を無視するため多用は推奨されません。外部ストアのデータや DOM 外部 API を同期させる場面でどうしても必要になるケースがありますが、その他は標準の state・props フローを使う方が明確で保守性も高いです。
パフォーマンス最適化の実践ベストプラクティス
再描画条件を把握した上で、実際に現場で使える最適化のベストプラクティスを紹介します。これらの方法を組み合わせて使うことで、React アプリケーションの描画効率を大幅に改善できます。
不要な render の原因を測定する
Profiler を使ってどのコンポーネントが頻繁に再描画されているかを調べ、どの props や state が原因かを特定します。最初から最適化を全て導入するのではなく、問題が顕在化している箇所から着手するとコスト効率が良いです。
コンポーネントの分割と責務の限定
大きなコンポーネントを小さく分割し、それぞれが独立した責務を持つよう設計すると、1つの state 更新が再描画を必要とする範囲を限定できます。また分離することで memo や callback 最適化が効きやすくなります。
Prop と state の参照型の取扱いに注意
オブジェクト・配列・関数など参照型の値を props や state に使うときは再生成を避け、可能な限りメモ化してください。浅い比較を前提とする API が多いため、参照の安定化がパフォーマンスに大きく影響します。
Context の設計を見直す
Context を乱用せず、Context の値を細かく分割するか、セレクタを使って必要な部分だけ取得するように設計することで、Context 更新時の影響範囲を小さくできます。これによって再描画が多発する現象を抑制できます。
React 最新 API の活用
useSyncExternalStore や useDeferredValue といった新しい React の API を使うと、外部データソースとの同期や優先度調整をより柔軟に行えます。これらを活用することで描画の滑らかさや応答性を確保しやすくなります。
よくある誤解とその正しい認識
再描画に関する誤解があると、余計な最適化や不正確な判断が入り込みがちです。ここでは代表的な誤解事例と正しい理解をまとめます。
再描画=DOM の更新ではない
関数コンポーネントの再描画とは、コンポーネント関数が呼ばれて仮想DOMを再構築することを意味します。しかしその後の差分検出で実際の DOM ノードが変更されない場合は、ブラウザにとっての描画負荷は非常に小さいです。再描画が問題なのは、中で重い処理やネストされた木構造を持っているケースです。
浅い比較の範囲と深い比較のコスト
PureComponent や React.memo が行うのは浅い比較です。ネストしたオブジェクトや配列の内容まで比較するわけではないため、値が内部で変化していても参照が変わらなければ検出できない場合があります。深い比較は手間や計算コストが高いため、慎重に使うべきです。
Memoization のオーバーヘッドを過剰評価しない
useMemo や useCallback、React.memo を多用することでコードが複雑になり、かえってレンダリング性能そのものより比較コストの方が高くなることがあります。軽いコンポーネントや頻度の低い更新ではむしろデメリットになるため、まずは Profiler 等で問題を確認することが重要です。
不要な Effect の間違い
データ変換など render 内で完結できるものを useEffect で実装すると、毎回依存関係が変わって再描画や副作用が発生しやすくなります。Effect は外部との同期のためにあるものと理解し、可能ならレンダー時に値を計算するコードに変えることが推奨されます。
まとめ
React re-render 条件を理解することは、意図しない再描画を防ぎ、アプリのパフォーマンスを向上させる第一歩です。state の更新・親の再描画・context の変化などが再描画の主要なトリガーとなり、inline 関数や不安定な key、過剰な state リフトアップなどが不要な再描画を招きます。
最適化には React.memo や useCallback、useMemo、PureComponent、shouldComponentUpdate などを適材適所で活用することが有効です。また、Profiler を使ってどのコンポーネントがどれだけ再描画しているのかを測定することが非常に重要です。効果的にこれらの技術を使えば、より快適でレスポンスの良い React アプリケーションが構築できます。
PureComponent は props と state を浅く比較する shouldComponentUpdate を既に備えているクラス基底クラスです。条件が単純な場合には PureComponent を使う方がコードが簡潔になります。ただし、複雑なオブジェクトやネストが深い props には注意が必要です。
forceUpdate のリスクと使い所
forceUpdate を使うと React の再レンダリングサイクルを強制できますが、shouldComponentUpdate を無視するため多用は推奨されません。外部ストアのデータや DOM 外部 API を同期させる場面でどうしても必要になるケースがありますが、その他は標準の state・props フローを使う方が明確で保守性も高いです。
パフォーマンス最適化の実践ベストプラクティス
再描画条件を把握した上で、実際に現場で使える最適化のベストプラクティスを紹介します。これらの方法を組み合わせて使うことで、React アプリケーションの描画効率を大幅に改善できます。
不要な render の原因を測定する
Profiler を使ってどのコンポーネントが頻繁に再描画されているかを調べ、どの props や state が原因かを特定します。最初から最適化を全て導入するのではなく、問題が顕在化している箇所から着手するとコスト効率が良いです。
コンポーネントの分割と責務の限定
大きなコンポーネントを小さく分割し、それぞれが独立した責務を持つよう設計すると、1つの state 更新が再描画を必要とする範囲を限定できます。また分離することで memo や callback 最適化が効きやすくなります。
Prop と state の参照型の取扱いに注意
オブジェクト・配列・関数など参照型の値を props や state に使うときは再生成を避け、可能な限りメモ化してください。浅い比較を前提とする API が多いため、参照の安定化がパフォーマンスに大きく影響します。
Context の設計を見直す
Context を乱用せず、Context の値を細かく分割するか、セレクタを使って必要な部分だけ取得するように設計することで、Context 更新時の影響範囲を小さくできます。これによって再描画が多発する現象を抑制できます。
React 最新 API の活用
useSyncExternalStore や useDeferredValue といった新しい React の API を使うと、外部データソースとの同期や優先度調整をより柔軟に行えます。これらを活用することで描画の滑らかさや応答性を確保しやすくなります。
よくある誤解とその正しい認識
再描画に関する誤解があると、余計な最適化や不正確な判断が入り込みがちです。ここでは代表的な誤解事例と正しい理解をまとめます。
再描画=DOM の更新ではない
関数コンポーネントの再描画とは、コンポーネント関数が呼ばれて仮想DOMを再構築することを意味します。しかしその後の差分検出で実際の DOM ノードが変更されない場合は、ブラウザにとっての描画負荷は非常に小さいです。再描画が問題なのは、中で重い処理やネストされた木構造を持っているケースです。
浅い比較の範囲と深い比較のコスト
PureComponent や React.memo が行うのは浅い比較です。ネストしたオブジェクトや配列の内容まで比較するわけではないため、値が内部で変化していても参照が変わらなければ検出できない場合があります。深い比較は手間や計算コストが高いため、慎重に使うべきです。
Memoization のオーバーヘッドを過剰評価しない
useMemo や useCallback、React.memo を多用することでコードが複雑になり、かえってレンダリング性能そのものより比較コストの方が高くなることがあります。軽いコンポーネントや頻度の低い更新ではむしろデメリットになるため、まずは Profiler 等で問題を確認することが重要です。
不要な Effect の間違い
データ変換など render 内で完結できるものを useEffect で実装すると、毎回依存関係が変わって再描画や副作用が発生しやすくなります。Effect は外部との同期のためにあるものと理解し、可能ならレンダー時に値を計算するコードに変えることが推奨されます。
まとめ
React re-render 条件を理解することは、意図しない再描画を防ぎ、アプリのパフォーマンスを向上させる第一歩です。state の更新・親の再描画・context の変化などが再描画の主要なトリガーとなり、inline 関数や不安定な key、過剰な state リフトアップなどが不要な再描画を招きます。
最適化には React.memo や useCallback、useMemo、PureComponent、shouldComponentUpdate などを適材適所で活用することが有効です。また、Profiler を使ってどのコンポーネントがどれだけ再描画しているのかを測定することが非常に重要です。効果的にこれらの技術を使えば、より快適でレスポンスの良い React アプリケーションが構築できます。
コメント