プログラミング

プログラミング

Reactのパフォーマンスを最適化する方法 ~12のツールと秘訣~

2,246 views

読了時間 : 約10分41秒

フロントエンドの開発者には、UIのアップデート手順を合理化してくれるReactを好む方が多くいますが、かなり大きいサイズのReactアプリケーションはしばしば動作が鈍重になりがちです。悲しいことに、Reactのようなフロントエンドライブラリを追加しても、アプリのパフォーマンスがすぐに向上するわけではありません。そのためにはReactの適切な設定が必要なのです。

 

 

つまり、アプリのコンポーネントの動作を効果的に測定して最適化する方法が分かっていれば、Reactアプリケーションの動作速度を、すなわち反応速度(reacts)を大幅に改善することができます。このガイドでは、より流動的なユーザーエクスペリエンスを提供するのためのJavaScriptのベストプラクティスを紹介するのに加えて、Reactを最適化する方法を説明します。

 

 

 

 

Reactの仕組み

 

この記事を読んでいるのですから、あなたはおそらくReactに詳しいのでしょう。そうでない人のために念のため説明しておくと、Reactは、Vue.jsのような他のライブラリで一般的な機能である仮想DOMの構築と比較を可能にする、有名なフロンドエンドエンジニア用のJavaScriptライブラリです。

 

 

すべてのReactアプリケーションはには枝状に分岐して木構造になっているルートコンポーネントがあります。これらのコンポーネントは、実際にはUIのレンダリングを担う関数なのです。Reactは、ユーザーのブラウザにコンテンツを描画した後、ユーザーの操作やその他の要求を追跡し続け、仮想的にUIの再レンダリングを行ってくれます。そして、Reactは現在のUIと新しいUIとの間の違いを「diffing」アルゴリズムと呼ばれるものを使って調整します。このような方法で、コンテンツを初めから読み込みなおすことなく、ユーザーが閲覧している部分に必要な分だけの変更が加えられます。

 

 

この設定は、動作を遅くする可能性のあるDOMによる高価な内容の変更をを避けるものですが、このプロセスはたいてい、パフォーマンスに関する問題の根本的原因になっていることがあります。Diffingアルゴリズムを調整することによって、Reactを使うことによるメリットを全て引き出せるのです。

 

 

 

 

Reactの最適化 まずはじめに

 

アプリに変更を加える前に、いくつかの測定しなければならないことがあります。どこから手をつければいいのか分からなければ、改善効果が表れているか分からないでしょう。もしある一連のコードが全体の動作を遅くしていると思われる場合には、すぐにでも修正したいという誘惑に駆られるかもしれませんが、Reactのパフォーマンス測定ツールを使えば、改善の度合いを定量化でき、それによって最適化作業のどこに集中するべきなのかを知ることができるのです。Reactのバージョンが16よりも古い場合には、次の操作で、react-additions-perfライブラリのインストール、インポート、そして実行ができます

 

npm install –save-dev react-addons-perf
Import Perf from ‘react-addons-perf’
Perf.start();
// your app
Perf.stop();
Perf.printWasted();

 

すると以下のような表が表示され、コンポーネントがレンダリングされるのにかかる時間を確認することができるはずです。

 

 

バージョン16.xのReactを使用している場合には、ブラウザーのプロファイリングを利用するのがお勧めです。Reactでこの記事をチェックすることで、Chromeを使ってReactコンポーネントの動作を遅くしている犯人を割り出す方法を見てみましょう。

 

 

 

 

更新するべきか、しないでおくべきか

 

Reactは、デフォルトでは仮想DOM全体を自動的に実行・レンダリングしてから、全てのすべてのコンポーネントの状態の変更を一つずつチェックします。DOMのサイズが大きい、このプロセスにはより長い時間がかかるようになります。幸い、shouldComponentUpdateメソッドを使用して、Reactに再レンダリングを必要とするコンポーネントを認識させることができます。次のように動作します。

 

function shouldComponentUpdate(nextProps, nextState) {
   return true;
}

 

上記の関数がコンポーネントにtrueを返すたびに、render-diffプロセスが実行されます。コンポーネントが再レンダリングされなようするためには、falseを返すようにすればよいだけです。現在と将来の状態を比較し、コンポーネントを再レンダリングする必要があるかどうかを判断するためには、次のように設定します。

 

function shouldComponentUpdate(nextProps, nextState) {
   return nextProps.id !== this.props.id;
}

 

 

 

 

Reactアプリケーションを高速化するための秘訣

 

1.React.PureComponentsを使う

 

プリミティブデータのみを含むコンポーネントの場合には、shouldComponentUpdate()関数を自動的に実装するReact.PureComponentにすることによって、「表層比較」を行うことができます。通常のコンポーネントとは対照的に、これを使用するとReactの再レンダリングを高速化することができます。React.PureComponentsの詳細について知りたい場合には公式ドキュメントを参照してください。

 

2.不変データ構造データ構造の実装

 

React.PureComponentsに自動的に複雑な状態変化の有無を調べさせるには、オブジェクトを直接変更するのではなく、データが変更されたオブジェクトのコピーを作成する、不変データ構造を立ち上げなければなりません。これによって、変更の検出プロセスを簡単にすることができます。ToptalにはJavaScriptアプリケーションの不変データ構造を構築するための詳細なチュートリアルがあります。

 

 

3.不必要なソースコードを取り除く

 

Reactの最も優れた機能の一つは、コードの一部がバグやエラーを引き起こす可能性がある時に、自動的に開発者に警告を発するというものです。これは、非常に便利な機能なのですが、この手頃な機能を組み込むことが、アプリケーションのパフォーマンス全体の低下を招くことがあります。ありがたいことに、これを避ける方法があるのです。

 

Reactのソースコードを徹底的に調べていくと、

process.env.NODE_ENV != ‘production’

という表示が画面いっぱいに繰り返し現れるはずです。これらのスニペットは、エラーを警告する役割を担っているのです。これらの命令は、エンドユーザーに利益をもたらすことはないので、開発過程で削除してしまってもかまいません。(注意!意図せぬ変更がアプリケーションをクラッシュする原因となる可能性があるため、Reactのソースコードを編集する際には、細心の注意を払って行ってください。)

 

もしcreate-react-appを使ってプロジェクトを進めた場合には、以下のコードを実行するだけで余計なコードのないビルドを行うことができます。

 

npm run build

 

Webpackで作業をしている場合には、webpack –pを実行することで、同じ結果が得られます。

 

 

4.定数とインライン要素を用いる

 

React Constant ElementsはJSX要素を値のように扱うため、その要素をより高いスコープへと運び、React,createClassの呼び出しを減らします。Reactのインライン要素はJSX要素を、戻るべきオブジェクトリテラルへと変換することによって、おなじ目標を達成します。

 

この設定を行うには、package.jsonファイルに以下の設定を追加します。

 

“babel”: {
   “env”: {
      “production”: {
         “plugins”: [
           “transform-react-constant-elements”,
           “transform-react-inline-elements”
         ]
      }
   }
},

 

 

5.チャンキーになろう

 

開発者の多くは、フロントエンドのJavaScriptのコードを出来る限り小さいファイルにバンドルしたいと考えています。小規模なReactアプリケーションであれば、これで良いのですが、プロジェクトが成長するにつれ、バンドルされたJavaScriptファイルをユーザーのブラウザに送信する処理に時間がかかるようになってきます。Webpackユーザーは、組み込みのコード分割機能を利用し、JavaScriptを、必要に応じてブラウザへ送信できるチャンクに分解することができます。Webpackのドキュメントには、コード分割に関する詳細なチュートリアルが含まれています。

 

6.GzipかBrotli圧縮を使う

 

JavaScriptファイルをより高速にロードするもう一つの方法は、WebサーバーでのGzip、

あるいはBrotliを使用できるようにすることです。GzipとBrotli圧縮はともにクライアントのデータ使用量を大幅に削減し、そのためレンダリング時間が短縮されます。最高の圧縮効果を得たいのならGzipよりもBrotliの方が良いでしょう

 

さらに、Brotliで圧縮された資源をサポートし、キャッシュするCDNを選びましょう。

 

 

7.Eslint-plugin-reactを使う

 

ESLintをJavaScriptによる全てのプロジェクトにまだ使用していないのなら、今すぐそうするのが良いでしょう。Eslint-plugin-reactは強制的にベストプラクティスへと導いてくれるので、プログラミングの初心者にとって特に有益です。それゆえ、ESLintを定期的に使用することで、将来コーディングスキルが向上しているかもしれません。

 

 

8.高次コンポーネントを呼び出す

 

Recomposeライブラリには不必要なレンダリングを制限する目的で呼び出すことのできる高次コンポーネントがいくつか含まれています。例えば、以下の設定で、propに変更があった時にだけ再レンダリングを行うようにできます。

 

@pure
class MyComponent extends Component {
   render() {
      ///…
   }
}

 

 

仮にprop1、またはprop2が変更された時にコンポーネントを再レンダリングし、prop3が変更された場合には再レンダリングを行わないようにしたいとします。これは次に示す設定により実現できます。

 

 

@onlyUpdateForKeys([‘prop1’, ‘prop2’])
class MyComponent extends Component {
   render() {
     ///…
   }
}

 

 

 

9.Connect()を使う

 

 

Reduxを使っている場合には、より動作をシンプルにするために、高次コンポーネントconnect()を好きな時に使用できます。連動するコンポーネントは、指定された値が変更された場合に限り、レンダリングされるようになれます。例えば、以下のスニペットはprop1が変更されたときのみコンポーネントが再レンダリングをされるようにするものです。

 

 

connect(state => ({
  prop1: state.prop1
}))(SomeComponent)

 

 

注意してほしいのは、mapStateToProps関数が計算を行わなければならない場合、再レンダリングが発生する可能性があるということです。例えば、以下のコードは不必要な再レンダリングが発生する可能性があります。

connect(state => ({
   computedData: {
     height: state.height,
     width: state.width
   }
}))(SomeComponent)

 

 

この問題を回避するには、以下のように、reselect関数を使って派生状態に対する依存関係を宣言します。

 

 

import {createSelector} from ‘reselect’
const selectComputedData = createSelector(
   state => state.height,
   state => state.width,
   (height, width) => ({
     height,
     width
   })
)
connect(state => ({
   computedData: selectComputedData(state)
}))(SomeComponent)

 

 

 

 

 

 

Reactを最適化する Reactを軽くするためのツール

 

1.why-did-you-update

why-did-you-updateライブラリは不要なコンポーネントのレンダリングを検出してくれます。具体的には、変更されていないのにも関わらずコンポーネントのレンダリングメソッドが呼び出されたインスタンスを突き止めることができます。npmインストールで簡単にセットアップすることができます。

 

npm install –save why-did-you-update

 

次に、以下のスニペットをアプリケーションのコードに挿入します。

 

 

import React from ‘react’

if (process.env.NODE_ENV !== ‘production’) {
   const {whyDidYouUpdate} = require(‘why-did-you-update’)
   whyDidYouUpdate(React)
}

 

 

ユーザーエンドでの動作を遅くしてしまわないように、最終的ビルド段階でこの機能は無効にしてください。

 

 

2.React Developer Tools

Chromeの拡張機能であるReact Developer Toolsは、不必要なレンダリングサイクルを特定するのに役立つコンポーネントの更新をハイライト表示してくれます。拡張機能のインストール後、クロームのDevToolsの中にあるReactのタブを選択して開き、更新をハイライト表示にするためのチェックボックスをオンにします。

 

 

 

プロジェクト進行中に、コンポーネントの再レンダリングが青、緑、黄、あるいは赤色でハイライト表示されるようになったはずです。寒色はそのコンポーネントの更新頻度が低いことを、そして暖色はおり更新頻度が高いことを示しています。

 

そのため、スライダーのようなUI要素の周囲には黄色と赤色の部分が多く見られるはずです。ボタンを1回クリックするだけで赤色が表示されるようになるのなら、おそらく不必要なレンダリングが行われているでしょう。

 

 

3.ChromeのDevTools パフォーマンスタイムライン

お使いのReactのバージョンが15.4.0か、それより新しいものであるならば、Chromeのパフォーマンスタイムラインを利用して、コンポーネントのマウント、更新、そしてマウント解除のタイミングを特定しましょう。

 

 

付け加えると、コンポーネントのライフサイクルを視覚化し、比較することもできます。この機能はUser Timing APIに依存しているため、Chrome、Edge、Internet Explorer上のみで動作します。パフォーマンスタイムラインの使い方はDevYoolsのウェブサイトで確認してください。

 

 

 

要約

Reactアプリケーションを最高の状態にチューニングするためのカギとなるのは、本当に必要な場合に限りコンポーネントが更新されるようにすることです。言うまでもないのですが、特にアプリに大幅な変更を加えた際には、常にパフォーマンスの最適化を常に優先するようにしてください。そして、変更後は進行状況を確認するために、ベンチマークを実行することをお忘れなく。

 

 

※この記事はOptimizing React Performance -12 Tools and Tipsを翻訳・再構成したものです。

 

 

▼こちらの記事もおすすめです!

 

 

おすすめ新着記事

おすすめタグ