Reactアプリのパフォーマンスを改善および最適化する方法

公開: 2022-03-10
簡単な要約↬Reactが導入されて以来、フロントエンド開発者がWebアプリケーションを構築する方法を変革し、その仮想DOMはコンポーネントを効果的にレンダリングすることで有名です。 このチュートリアルでは、Reactアプリケーションのパフォーマンスを最適化するさまざまな方法と、パフォーマンスを向上させるために使用できるReactの機能について説明します。

Reactを使用すると、Webアプリケーションでユーザーインターフェイス(UI)をすばやく更新できますが、中規模または大規模のReactアプリケーションが効率的に実行されるわけではありません。 そのパフォーマンスは、Reactを構築するときにどのように使用するか、およびReactがどのように動作するか、およびコンポーネントがライフサイクルのさまざまなフェーズを通過するプロセスについての理解に依存します。 Reactは、Webアプリのパフォーマンスを大幅に向上させます。これらの向上は、さまざまな手法、機能、およびツールを通じて実現できます。

このチュートリアルでは、Reactアプリケーションのパフォーマンスを最適化するさまざまな方法と、パフォーマンスを向上させるために使用できるReactの機能について説明します。

Reactアプリケーションのパフォーマンスの最適化をどこから始めればよいですか?

いつどこで最適化するかを正確に知らなければ、アプリの最適化を開始することはできません。 「どこから始めればいいの?」と聞かれるかもしれません。

最初のレンダリングプロセス中に、ReactはコンポーネントのDOMツリーを構築します。 したがって、DOMツリーでデータが変更された場合、Reactは、変更の影響を受けたコンポーネントのみを再レンダリングし、影響を受けなかったツリー内の他のコンポーネントをスキップする必要があります。

ただし、すべてが影響を受けるわけではありませんが、ReactはDOMツリー内のすべてのコンポーネントを再レンダリングすることになります。 これにより、読み込み時間が長くなり、時間が無駄になり、CPUリソースが無駄になります。 これを防ぐ必要があります。 したがって、ここで最適化の取り組みに焦点を当てます。

この状況では、リソースと時間を無駄にしないように、必要な場合にのみレンダリングまたは差分するようにすべてのコンポーネントを構成できます。

ジャンプした後もっと! 以下を読み続けてください↓

性能測定

感じたことに基づいてReactアプリケーションの最適化プロセスを開始しないでください。 代わりに、利用可能な測定ツールを使用してReactアプリのパフォーマンスを分析し、速度を低下させている可能性のあるものの詳細なレポートを取得してください。

Chromeの[パフォーマンス]タブを使用したReactコンポーネントの分析

Reactのドキュメントによると、開発モードのままで、Chromeブラウザの[パフォーマンス]タブを使用して、Reactコンポーネントのマウント、更新、アンマウントの方法を視覚化できます。 たとえば、次の画像は、開発モードでのブログのプロファイリングと分析を行うChromeの[パフォーマンス]タブを示しています。

パフォーマンスプロファイラーの概要
パフォーマンスプロファイラーの概要(大きなプレビュー)

これを行うには、次の手順に従います。

  1. すべての拡張機能、特にReact Developer Toolsを一時的に無効にします。これは、拡張機能が分析結果を台無しにする可能性があるためです。 ブラウザをシークレットモードで実行すると、拡張機能を簡単に無効にできます。
  2. アプリケーションが開発モードで実行されていることを確認してください。 つまり、アプリケーションはローカルホストで実行されている必要があります。
  3. Chromeの開発ツールを開き、[パフォーマンス]タブをクリックしてから、[記録]ボタンをクリックします。
  4. プロファイルするアクションを実行します。 20秒を超えて記録しないでください。そうしないと、Chromeがハングする可能性があります。
  5. 録音を停止します。
  6. Reactイベントは、「UserTiming」ラベルの下にグループ化されます。

プロファイラーからの数値は相対的なものです。 ほとんどの場合、コンポーネントは本番環境でより迅速にレンダリングされます。 それでも、これは、UIが誤って更新された時期、およびUIの更新が発生する深さと頻度を把握するのに役立ちます。

React Developer Tools Profiler

Reactのドキュメントによると、 react-dom dom16.5 +およびreact-native native0.57 +では、React Developer ToolsProfilerを使用して開発者モードで拡張プロファイリング機能を利用できます。 プロファイラーは、Reactの実験的なプロファイラーAPIを使用して、レンダリングされた各コンポーネントに関するタイミング情報を照合し、Reactアプリケーションのパフォーマンスのボトルネックを特定します。

ブラウザ用のReactDeveloper Toolsをダウンロードするだけで、付属のプロファイラーツールを使用できます。 プロファイラーは、開発モードまたはReact v16.5 +の本番プロファイリングビルドでのみ使用できます。 以下の画像は、React Developer ToolsProfilerを使用した開発モードでのブログのプロファイラーの概要です。

React Developer ToolsProfilerフレームグラフ
React Developer Toolsプロファイラーフレームグラフ(大プレビュー)

これを実現するには、次の手順に従います。

  1. React開発者ツールをダウンロードします。
  2. Reactアプリケーションが開発モードまたはReactv16.5 +の本番プロファイリングビルドのいずれかであることを確認してください。
  3. Chromeの[デベロッパーツール]タブを開きます。 React Developer Toolsが提供する、「プロファイラー」という名前の新しいタブが利用可能になります。
  4. 「記録」ボタンをクリックして、プロファイリングするアクションを実行します。 理想的には、プロファイリングするアクションを実行した後で記録を停止します。
  5. グラフ(フレームグラフと呼ばれます)が、Reactアプリのすべてのイベントハンドラーとコンポーネントとともに表示されます。

詳細については、ドキュメントを参照してください。

React.memo React.memo()によるメモ化

React v16は、追加のAPI、 React.memo()と呼ばれる高次コンポーネントとともにリリースされました。 ドキュメントによると、これはパフォーマンスの最適化としてのみ存在します。

その名前「メモ」はメモ化に由来します。メモ化は、基本的に、高価な関数呼び出しの結果を保存し、同じ高価な関数が再度呼び出されるたびに保存された結果を返すことによってコードを高速化するために使用される最適化の形式です。

メモ化は、関数(通常は純粋関数)を1回実行し、その結果をメモリに保存するための手法です。 以前と同じ引数を使用してその関数を再度実行しようとすると、関数を再度実行することなく、最初の関数の実行から以前に保存された結果が返されます。

上記の説明をReactエコシステムにマッピングすると、言及されている関数はReactコンポーネントであり、引数は小道具です。

React.memo()を使用して宣言されたコンポーネントのデフォルトの動作は、コンポーネントの小道具が変更された場合にのみレンダリングされることです。 これをチェックするために小道具の浅い比較を行いますが、これをオーバーライドするオプションが利用可能です。

React.memo()は、小道具が変更されていないコンポーネントや再レンダリングが不要なコンポーネントの再レンダリングを回避することで、Reactアプリのパフォーマンスを向上させます。

以下のコードは、 React.memo()の基本的な構文です。

 const MemoizedComponent = React.memo((props) => { // Component code goes in here })

React.memo()を使用する場合

  • 純粋機能コンポーネント
    コンポーネントが機能し、同じ小道具が与えられ、常に同じ出力をレンダリングする場合は、 React.memo()を使用できます。 Reactフックを使用して、純粋に機能しないコンポーネントでReact.memo()を使用することもできます。
  • コンポーネントは頻繁にレンダリングします
    React.memo()を使用して、頻繁にレンダリングされるコンポーネントをラップできます。
  • コンポーネントは同じ小道具で再レンダリングされます
    React.memo()を使用して、再レンダリング中に通常同じ小道具が提供されるコンポーネントをラップします。
  • 中〜高要素
    中程度から多数のUI要素を含むコンポーネントに使用して、小道具が等しいかどうかを確認します。

小道具をコールバックとして使用するコンポーネントをメモ化するときは注意してください。 レンダリング間で必ず同じコールバック関数インスタンスを使用してください。 これは、親コンポーネントがレンダリングごとにコールバック関数の異なるインスタンスを提供する可能性があるためです。これにより、メモ化プロセスが中断します。 これを修正するには、メモ化されたコンポーネントが常に同じコールバックインスタンスを受け取るようにします。

実際の状況でメモ化を使用する方法を見てみましょう。 以下の「写真」と呼ばれる機能コンポーネントは、 React.memo()を使用して再レンダリングを防ぎます。

 export function Photo({ title, views }) { return ( <div> <div>Photo title: {title}</div> <div>Location: {location}</div> </div> ); } // memoize the component export const MemoizedPhoto = React.memo(Photo);

上記のコードは、写真のタイトルと写真内の被写体の位置を含むdivを表示する機能コンポーネントで構成されています。 また、新しい関数を作成してMemoizedPhotoと呼ぶことにより、コンポーネントをメモ化しています。 写真コンポーネントをメモ化すると、小道具、 title 、およびlocationが後続のレンダリングで同じである限り、コンポーネントが再レンダリングされなくなります。

 // On first render, React calls MemoizedPhoto function. <MemoizedPhoto title="Effiel Tower" location="Paris" /> // On next render, React does not call MemoizedPhoto function, // preventing rendering <MemoizedPhoto title="Effiel Tower" location="Paris" />

ここで、Reactはメモ化された関数を1回だけ呼び出します。 小道具が同じである限り、次の呼び出しでコンポーネントをレンダリングしません。

バンドルとミニファイ

Reactシングルページアプリケーションでは、すべてのJavaScriptコードを1つのファイルにバンドルして縮小できます。 アプリケーションが比較的小さい限り、これは問題ありません。

Reactアプリケーションが大きくなるにつれて、すべてのJavaScriptコードを1つのファイルにバンドルして縮小することは、問題があり、理解が難しく、面倒になります。 また、大きなJavaScriptファイルをブラウザーに送信するため、Reactアプリのパフォーマンスと読み込み時間にも影響します。 そのため、コードベースをさまざまなファイルに分割し、必要に応じて間隔を置いてブラウザに配信するためのプロセスが必要です。

このような状況では、Webpackのような何らかの形式のアセットバンドラーを使用し、そのコード分割機能を利用してアプリケーションを複数のファイルに分割できます。

コード分​​割は、アプリケーションのロード時間を改善する手段としてWebpackのドキュメントで提案されています。 また、遅延読み込み(ユーザーが現在必要としているもののみを提供する)に関するReactのドキュメントでも提案されており、パフォーマンスを劇的に向上させることができます。

Webpackは、コード分割に対する3つの一般的なアプローチを提案しています。

  • エントリポイント
    エントリ構成を使用してコードを手動で分割します。
  • 重複防止
    SplitChunksPluginを使用して、チャンクを重複排除および分割します。
  • 動的インポート
    モジュール内のインライン関数呼び出しを介してコードを分割します。

コード分​​割の利点

  • コードの分割は、ブラウザのキャッシュリソースと、頻繁に変更されないコードを支援します。
  • また、ブラウザがリソースを並行してダウンロードするのに役立ち、アプリケーションの全体的な読み込み時間を短縮します。
  • これにより、コードをチャンクに分割して、オンデマンドまたはアプリケーションの必要に応じてロードすることができます。
  • 最初のレンダリングでのリソースの初期ダウンロードが比較的小さく保たれるため、アプリの読み込み時間が短縮されます。
バンドルとミニファイのプロセス
バンドルとミニファイのプロセス(大規模なプレビュー)

不変のデータ構造

Reactのドキュメントでは、データを変更しないことの力について説明しています。 変更できないデータは不変です。 不変性は、Reactプログラマーが理解する必要のある概念です。

不変の値またはオブジェクトは変更できません。 したがって、更新があると、新しい値がメモリに作成され、古い値は変更されません。

不変のデータ構造とReact.PureComponentを使用して、複雑な状態変化を自動的にチェックできます。 たとえば、アプリケーションの状態が不変である場合、実際にはすべての状態オブジェクトをReduxなどの状態管理ライブラリを使用して単一のストアに保存できるため、元に戻す機能とやり直し機能を簡単に実装できます。

一度作成された不変データは変更できないことを忘れないでください。

不変のデータ構造の利点

  • 副作用はありません。
  • 不変のデータオブジェクトは、作成、テスト、および使用が簡単です。
  • これらは、データを何度もチェックすることなく、状態の更新をすばやくチェックするために使用できるロジックを作成するのに役立ちます。
  • これらは、一時的な結合(コードが実行の順序に依存する結合のタイプ)を防ぐのに役立ちます。

次のライブラリは、不変のデータ構造のセットを提供するのに役立ちます。

  • 不変性-ヘルパー
    ソースを変更せずにデータのコピーを変更します。
  • Immutable.js
    JavaScriptの不変の永続データ収集により、効率とシンプルさが向上します。
  • シームレス-不変
    JavaScriptの不変データ構造は、通常のJavaScript配列およびオブジェクトとの下位互換性があります。
  • React-コピー-書き込み
    これにより、可変APIで不変の状態が得られます。

パフォーマンスを改善する他の方法

デプロイ前に本番ビルドを使用する

Reactのドキュメントは、アプリをデプロイするときに縮小された本番ビルドを使用することを提案しています。

React DeveloperToolsの「本番ビルド」警告
React Developer Toolsの「本番ビルド」警告(大プレビュー)

匿名関数を避ける

匿名関数には( const/let/varを介して)識別子が割り当てられないため、コンポーネントが必然的に再度レンダリングされるたびに永続化されません。 これにより、名前付き関数が使用されている場合のように1つのメモリを一度だけ割り当てるのではなく、このコンポーネントが再レンダリングされるたびにJavaScriptが新しいメモリを割り当てます。

 import React from 'react'; // Don't do this. class Dont extends Component { render() { return ( <button onClick={() => console.log('Do not do this')}> Don't </button> ); } } // The better way class Do extends Component { handleClick = () => { console.log('This is OK'); } render() { return ( <button onClick={this.handleClick}> Do </button> ); } }

上記のコードは、ボタンがクリック時にアクションを実行するようにする2つの異なる方法を示しています。 最初のコードブロックはonClick()プロップで無名関数を使用しており、これはパフォーマンスに影響します。 2番目のコードブロックは、 onClick()関数で名前付き関数を使用します。これは、このシナリオでは正しい方法です。

多くの場合、コンポーネントの取り付けと取り外しには費用がかかります

条件またはテナリを使用してコンポーネントを非表示にする(つまり、アンマウントする)ことはお勧めできません。コンポーネントを非表示にすると、ブラウザが再描画およびリフローするためです。 ドキュメント内のHTML要素の位置とジオメトリを再計算する必要があるため、これはコストのかかるプロセスです。 代わりに、CSSのopacityvisibilityのプロパティを使用してコンポーネントを非表示にすることができます。 このように、コンポーネントは引き続きDOMにありますが、パフォーマンスを犠牲にすることなく非表示になります。

長いリストを仮想化する

ドキュメントでは、大量のデータを含むリストをレンダリングする場合は、表示されているビューポート内で一度にリスト内のデータのごく一部をレンダリングする必要があると提案されています。 次に、リストがスクロールされているときに、より多くのデータをレンダリングできます。 したがって、データはビューポートにある場合にのみ表示されます。 このプロセスは「ウィンドウ処理」と呼ばれます。 ウィンドウ処理では、行の小さなサブセットがいつでもレンダリングされます。 これを行うための人気のあるライブラリがあり、そのうちの2つはBrianVaughnによって管理されています。

  • 反応ウィンドウ
  • 反応仮想化

結論

Reactアプリケーションのパフォーマンスを向上させる方法は他にもいくつかあります。 この記事では、パフォーマンスを最適化するための最も重要で効果的な方法について説明しました。

このチュートリアルをお楽しみいただけたでしょうか。 詳細については、以下のリソースをご覧ください。 ご不明な点がございましたら、下のコメント欄にご記入ください。 私はそれらのすべてに喜んで答えます。

参考資料と関連リソース

  • 「パフォーマンスの最適化」、React Docs
  • 「React.memoを賢く使う」、Dmitri Pavlutin
  • 「Reactのパフォーマンス最適化手法」、Niteesh Yadav
  • 「Reactの不変性:変異オブジェクトには何も問題はありません」、Esteban Herrera
  • 「Reactアプリのパフォーマンスを最適化する10の方法」、Chidume Nnamdi
  • 「Reactアプリのパフォーマンスを向上させるための5つのヒント」、William Le