Reactアプリの6つの最適化のヒント
- はじめにここ数年で、JavaScriptフレームワークはアプリの構築方法を完全に変え、Reactは変換プロセスのかなりの部分を占めてきました。 ページの読み込みにかかる時間はバウンス率とコンバージョン率に直接関係するため、ページの読み込み時間を最適化することは重要です。 このチュートリアルでは、ほとんどの開発者がReactを使用してアプリを構築するときに犯す6つのよくある間違いを見ていきます。 また、これらの間違いを回避する方法について説明し、ページの読み込み時間をできるだけ短くするための役立つヒントを紹介します。
Reactのしくみ
上の画像は、Reactアプリを使用するたびに何が起こるかを説明しています。 すべてのReactアプリケーションは、ルートコンポーネント(この場合はApp
)で始まり、「子」コンポーネント(AddGrocery
、GroceryList
、SearchBar
)で構成されます。 ]。 これらの子コンポーネントは、それらに含まれるプロパティと状態に基づいてUIをDOMにレンダリングする関数で構成されています。
ユーザーは、フォームに入力するかボタンをクリックするなど、さまざまな方法でレンダリングされたUIを操作します。 これが発生すると、イベントは親コンポーネントに中継されます。 これらのイベントにより、アプリケーションの状態が変化し、Reactが仮想DOMでUIを再レンダリングするように促されます。
Reactのエンジンは、返送されるすべてのイベントについて、仮想DOMを実際のDOMと比較し、この新しく見つかったデータで実際のDOMを更新する必要があるかどうかを計算する必要があります。 さて、物事が厄介になるのは、クリックとスクロールのたびにReactが仮想DOMと実際のDOMの間で変更を繰り返し比較および更新するようにアプリが構成されている場合です。 これにより、アプリケーションが非常に遅く非効率になる可能性があります。
よくある間違いとそれを回避する方法
以下は、開発者がアプリケーションを構築するときに行う3つの悪い習慣です。 これらの間違いは効率を低下させ、全体的にページの読み込み時間を増やします。できるだけ避けてください。
不必要な輸入
アプリケーションにライブラリ全体をインポートするときはいつでも、必要なモジュールにアクセスするためにライブラリが分解されます。 1つまたは2つの小さなライブラリは害を及ぼさない場合がありますが、多数のライブラリと依存関係をインポートする場合は、必要なモジュールをインポートするだけです。
import assign from "101"; import Map from "immutable";
上記のコードは、ライブラリ全体をインポートし、assign
およびMap
にアクセスするためにライブラリの構造を解除し始めます。 その代わりに、アプリケーションに必要な部分だけを取得する「チェリーピッキング」と呼ばれる概念を使用しましょう。
import assign from "101/assign"; import Map from "immutable/src/map";
JSXへの関数の埋め込み
JavaScriptは、ガベージコレクション言語としてよく知られています。 そのガベージコレクタは、アプリケーションの一部で使用されなくなったメモリを再利用しようとすることでメモリを管理します。 レンダリングで関数を定義すると、含まれているコンポーネントが再レンダリングされるたびに、関数の新しいインスタンスが作成されます。 この方法がアプリケーション全体で発生すると、最終的にはメモリリークの形でガベージコレクターにとって問題になります。アプリケーションの一部で使用されていたメモリが、それらの部分で使用されなくなっても解放されない状況です。
class GroceryList extends React.Component { state = { groceries: [], selectedGroceryId: null } render(){ const { groceries } = this.state; return ( groceries.map((grocery)=>{ return <Grocery onClick={(e)=>{ this.setState({selectedGroceryId:grocery.groceryId}) }} grocery={grocery} key={grocery.id}/> }) ) } }
正しいことは、render
の直前に新しい関数onGroceryClick
を定義することです。
class GroceryList extends React.Component { state = { groceries: [], selectedGroceryId: null } onGroceryClick = (groceryId)=>{ this.setState({selectedGroceryId:groceryId}) } render(){ const { groceries } = this.state; return ( groceries.map((grocery)=>{ return <Grocery onClick={this.onGroceryClick} grocery={grocery} key={grocery.id}/> }) ) } }
DOM要素でのSpread演算子の使用
アプリを作成するときは、spread演算子を自由に使用することはお勧めできません。 これには未知の属性が飛び交うことになり、物事が複雑になり始めるのは時間の問題です。 簡単な例を次に示します。
const GroceriesLabel = props => { return ( <div {...props}> {props.text} </div> ); };
構築しているアプリケーションが大きくなると、. . .props
には何でも含まれる可能性があります。 より適切で安全な方法は、必要な値を特定することです。
const GroceriesLabel = props => { return ( <div particularValue={props.particularValue}> {props.text} </div> ); };
パフォーマンスハック
Gzipを使用してバンドルを圧縮する
これは非常に効率的で、ファイルのサイズを最大65%削減できます。 アプリケーション内のほとんどのファイルは、多くの繰り返しテキストと空白を使用します。 Gzipは、これらの繰り返し文字列を圧縮することでこれを処理し、Webサイトの最初のレンダリング時間を大幅に短縮します。 Gzipは、compressionPlugin
を使用してファイルを事前圧縮します—本番環境でファイルを圧縮するためのWebpackのローカルプラグインです。まず、compressionPlugin
を使用して圧縮バンドルを作成しましょう。
plugins: [ new CompressionPlugin({ asset: "[path].gz[query]", algorithm: "gzip", test: /.js$|.css$|.html$/, threshold: 10240, minRatio: 0.8 }) ]
ファイルが圧縮されると、ミドルウェアを使用して提供できます。
//this middleware serves all js files as gzip app.use(function(req, res, next) { const originalPath = req.path; if (!originalPath.endsWith(".js")) { next(); return; } try { const stats = fs.statSync(path.join("public", `${req.path}.gz`)); res.append('Content-Encoding', 'gzip'); res.setHeader('Vary', 'Accept-Encoding'); res.setHeader('Cache-Control', 'public, max-age=512000'); req.url = `${req.url}.gz`; const type = mime.lookup(path.join("public", originalPath)); if (typeof type != 'undefined') { const charset = mime.charsets.lookup(type); res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : '')); } } catch (e) {} next(); })
Reactコンポーネントのメモ化
メモ化の概念は新しいものではありません。これは、高価な関数呼び出しを保存し、同じ入力が再度発生したときにキャッシュされた結果を返す手法です。 Reactでのメモ化は、データ計算をメモ化することで機能し、状態の変化が可能な限り迅速に発生するようにします。 これがどのように機能するかを理解するには、以下のReactコンポーネントを見てください。
const GroceryDetails = ({grocery, onEdit}) => { const {name, price, grocery_img} = grocery; return ( <div className="grocery-detail-wrapper"> <img src={grocery_img} /> <h3>{name}</h3> <p>{price}</p> </div> ) }
上記のコードサンプルでは、GroceryDetails
のすべての子は小道具に基づいています。 小道具を変更すると、GroceryDetails
が再レンダリングされます。 GroceryDetailsが変更される可能性が低いコンポーネントである場合は、メモ化する必要があります。 以前のバージョンのReact(moize 、JavaScriptのメモ化ライブラリ。 以下の構文を見てください。
import moize from 'moize/flow-typed'; const GroceryDetails = ({grocery, onEdit}) =>{ const {name, price, grocery_img} = grocery; return ( <div className="grocery-detail-wrapper"> <img src={grocery_img} /> <h3>{name}</h3> <p>{price}</p> </div> ) } export default moize(GroceryDetails,{ isReact: true });
上記のコードブロックでは、moize
は渡された小道具とコンテキストをGroceryDetails
に格納し、両方を使用してコンポーネントに更新があるかどうかを検出します。 小道具とコンテキストが変更されない限り、キャッシュされた値が返されます。 これにより、非常に高速なレンダリングが保証されます。
それ以降のバージョンのReact(V16.6.0以降)を使用しているユーザーの場合、moizeの代わりにReact.memo
を使用できます。
const GroceryDetails = ({grocery, onEdit}) =>{ const {name, price, grocery_img} = grocery; return ( <div className="grocery-detail-wrapper"> <img src={grocery_img} /> <h3>{name}</h3> <p>{price}</p> </div> ) } export default React.memo(GroceryDetails);
サーバーサイドレンダリングの使用を検討してください
サーバーサイドレンダリング(SSR)は、バックエンドシステムで実行中にマークアップをレンダリングするフロントエンドフレームワークの機能です。
シングルページアプリケーションでSSRを活用する必要があります。 ユーザーがJavaScriptファイルの読み込みを待つのではなく、アプリケーションのユーザーは、送信された最初のリクエストがレスポンスを返すとすぐに、完全にレンダリングされたHTMLページを受け取ります。 一般に、サーバー側でレンダリングされたアプリケーションを使用すると、ユーザーはクライアントでレンダリングされたアプリケーションよりもはるかに高速にコンテンツを受信できます。 Reactでのサーバー側レンダリングのソリューションには、Next.jsとGatsbyが含まれます。
結論
これらの概念に精通していない平均的な初心者の方は、 React の使用を開始するためのこのコースをチェックすることをお勧めします。これは、状態の処理からコンポーネントの作成まで、1トンをカバーします。 Reactアプリケーションでページの読み込み時間を最適化することの重要性を過小評価することはできません。 アプリがより良いSEOを促進し、より高いコンバージョン率を実現するだけでなく、ベストプラクティスに従うことで、エラーをより簡単に検出できるようになります。 これらの概念には多くの努力が必要かもしれませんが、試してみる価値はあります。 Tony QuetanoによるReactComponentsのメモ化や、SelvaGaneshによる本番環境でのWebpackgzipファイルの提供方法などの投稿がこれを書くのに役立ちました。