公式のReact.jsライブラリドキュメントのAdvanced Guidesセクションの一連の翻訳の継続。
Reactのパフォーマンス最適化
内部的に、Reactは、ユーザーインターフェイスの更新に必要なコストのかかるDOM操作の数を最小限に抑えるいくつかの高度な手法を使用しています。 Reactを使用するほとんどのアプリケーションでは、パフォーマンスを最適化するための追加手順なしで、結果のインターフェイスの速度で十分です。 ただし、Reactアプリケーションを高速化する方法はいくつかあります。
最終(生産)アセンブリを使用する
Reactアプリケーションでパフォーマンスをテストしている場合、またはパフォーマンスの問題が発生している場合は、縮小された実動アセンブリーを必ずテストしてください。
- Reactアプリケーションビルドをビルドするには、
npm run build
をnpm run build
、以下の手順に従う必要があります。 - 単一ファイルビルドの場合、拡張子
.min.js
最終ビルド用に準備されたReactバージョンを提供します。 - Browserifyコレクターの場合、
NODE_ENV=production
パラメーターを使用してアセンブリを実行する必要があります。 - Webpackビルダーの場合、最終アセンブリの構成ファイル(運用構成)に次のプラグインを追加する必要があります。
new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production') } }), new webpack.optimize.UglifyJsPlugin()
開発用アセンブリには、アプリケーションの開発に役立つ追加の警告が含まれていますが、追加のアクションを実行することでアプリケーションの速度が低下します。
Chromeのタイムラインを使用したコンポーネントのプロファイリング
サポートされているブラウザーの開発ツールでパフォーマンスツールを使用すると、コンポーネントのマウント、更新、アンマウントの方法を視覚化できます。 例:
Chromeで次の手順を実行します。
クエリ文字列に
?react_perf
パラメーターを指定してアプリケーションをダウンロードします(例:http://localhost:3000/?react_perf
)。
Chromeデベロッパーツールの[ タイムライン ]タブをクリックし、[ 記録 ]をクリックします。
プロファイリングする手順に従います。 20秒以上アクションを記録しないでください。そうしないと、Chromeがフリーズする可能性があります。
記録を停止します。
- ReactイベントはUser Timingラベルの下にグループ化されます 。
これらの数値は相対的であることに注意してください。 生産モードではコンポーネントがより速く表示されます 。 最終的に、これは、ユーザーインターフェイスが誤って更新を受信する場所、およびユーザーインターフェイスが更新される深さと頻度を想像するのに役立ちます。
現在、Chrome、Edge、およびIEのみがこの機能をサポートしていますが、 ユーザータイミングAPI標準を使用しているため、他のブラウザがこの機能のサポートを追加することを期待しています。
不要な再描画を避ける
Reactは、表示されたユーザーインターフェイスの内部表現を作成および維持します。 コンポーネントによって返されるReact要素が含まれます。 これにより、Reactは既存のDOMノードを作成して必要以上にアクセスすることを回避できます。これは、JavaScriptオブジェクトを使用した操作よりも遅くなる可能性があります。 Native React(React Native)も機能しますが、このモデルは「仮想DOM」と呼ばれることもあります。
コンポーネントのプロパティ(プロパティ)または状態が変化すると、Reactは要素の新しい返されたバージョンをDOMに表示された以前のバージョンと比較し、それらが同等でない場合、DOMを更新します。
場合によっては、再マッピング(レンダリング)プロセスの開始前に呼び出されるshouldComponentUpdate
ライフサイクルshouldComponentUpdate
オーバーライドすることにより、コンポーネントを高速化できます。 デフォルトの関数定義はtrue
返し、Reactが更新できるようにします。
shouldComponentUpdate(nextProps, nextState) { return true; }
場合によってはコンポーネントを更新する必要がないことがわかっている場合、 shouldComponentUpdate
関数からfalse
を返して、再レンダリング(レンダリング)のプロセスをスキップできます。これには、現在のコンポーネントおよび階層の下位でrender()
メソッドを呼び出すことも含まれます。
アクションのshouldComponentUpdate
この図は、コンポーネントのツリーを示しています。 SCU
ラベルはshouldComponentUpdate
関数によって返された結果を表示し、 vDOMEq
ラベルvDOMEq
React要素が前のビューと同等かどうかをvDOMEq
ます。 円の色は、コンポーネントを再描画する必要があることを示しています。
C2コンポーネントでは、 shouldComponentUpdate
関数はfalse
返しました。その結果、C2以下から始まるサブツリー全体で、Reactは再描画しません(レンダー関数を呼び出します)。このため、C4およびC5コンポーネントでshouldComponentUpdate
関数を呼び出す必要もありませんでした。
C1およびC3のtrue
、 shouldComponentUpdate
はtrue
返したため、Reactは下に移動して子孫を確認する必要がありました。 C6の場合、 shouldComponentUpdate
はtrue
返し、示された要素は仮想DOMと同等ではないため、ReactはDOMを更新する必要がありました。
さて、最後の興味深いオプションはC8です。 Reactはコンポーネントをレンダリングする必要がありましたが、 React要素の新しい内部表現は以前のものと同等であり、DOMを更新する必要はありませんでした。
ReactはC6のみのDOMを変更する必要があり、これは避けられないことに注意してください。 C8の場合-レンダリングされたReact要素を比較するのにshouldComponentUpdate
ツリーとC7コンポーネントでは、要素を比較する必要さえありませんでしたshouldComponentUpdate
はfalse
を返し、 render
メソッドは呼び出されませんでした。
例
props.color
プロパティまたはstate.count
状態state.count
ときにのみコンポーネントが変化すると想像してください。 shouldComponentUpdate
関数でこれらのケースの検証を実装できます。
class CounterButton extends React.Component { constructor(props) { super(props); this.state = {count: 1}; } shouldComponentUpdate(nextProps, nextState) { if (this.props.color !== nextProps.color) { return true; } if (this.state.count !== nextState.count) { return true; } return false; } render() { return ( <button color={this.props.color} onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button> ); } }
このコードでは、 shouldComponentUpdate
関数はprops.color
プロパティまたはstate.count
状態の変更をチェックします。 値が変更されていない場合、コンポーネントは更新されません。 コンポーネントがより複雑な場合は、同様のテンプレートを使用できますが、 props
と状態状態のすべてのプロパティ(フィールド)の「表面比較」を実行して、コンポーネントを更新する必要があるかどうかを判断します。 このテンプレートは非常に頻繁に使用されるため、Reactにはこのロジックを実装するヘルパーがあります。コンポーネントをReact.PureComponent
から継承するだけReact.PureComponent
。 次のコードは、前のものと同じ目標を達成しますが、それは本当に簡単です:
class CounterButton extends React.PureComponent { constructor(props) { super(props); this.state = {count: 1}; } render() { return ( <button color={this.props.color} onClick={() => this.setState(state => ({count: state.count + 1}))}> Count: {this.state.count} </button> ); } }
ほとんどの場合、独自のshouldComponentUpdate
関数をReact.PureComponent
代わりに、 React.PureComponent
を使用できます。 ただし、 React.PureComponent
は表面の比較のみを実装し、表面の比較では不十分な方法でプロパティ(プロパティ)または状態を変更できる場合があります。この状況は、より複雑なデータ構造で発生する可能性があります。 たとえば、 ListOfWords
コンポーネントを実装して、コンマで区切られた単語のリストを表示する必要があるとします。親コンポーネントのWordAdder
は、ボタンをクリックするとリストに単語を追加します。 次のコードは正しく機能しません。
class ListOfWords extends React.PureComponent { render() { return <div>{this.props.words.join(',')}</div>; } } class WordAdder extends React.Component { constructor(props) { super(props); this.state = { words: ['marklar'] }; this.handleClick = this.handleClick.bind(this); } handleClick() { // const words = this.state.words; words.push('marklar'); this.setState({words: words}); } render() { return ( <div> <button onClick={this.handleClick} /> <ListOfWords words={this.state.words} /> </div> ); } }
問題は、 PureComponent
がthis.props.words
配列の古い値と新しい値の単純で浅い比較を行うことです。 単純な比較は、両方の比較値が同じオブジェクト(配列)を参照している場合にtrue
返しtrue
。 handleClick
コンポーネントのhandleClick
メソッドの指定されたコードは、 words
配列をWordAdder
変更するため、 handleClick
の古い値と新しい値を比較すると、配列内の単語自体が変更されたとしても同等の値が返されます。 ListOfWords
コンポーネントListOfWords
更新されませんが、表示する必要がある新しい単語があります。
不変データの力
この問題を解決する最も簡単な方法は、プロパティ(props)または状態で使用する値を変更しない(変更しない)ことです。 たとえば、 concat
メソッドを使用してhandleClick
メソッドを書き換えることができますhandleClick
メソッドは、新しい値を追加した配列の浅いコピーを返します。
handleClick() { this.setState(prevState => ({ words: prevState.words.concat(['marklar']) })); }
ES6は配列のスプレッド構文をサポートしているため、コードがさらに簡単になります。 Create React Appを使用してアプリケーションを作成する場合、この構文はデフォルトで使用可能です。
handleClick() { this.setState(prevState => ({ words: [...prevState.words, 'marklar'], })); };
同様に、オブジェクトの変更(突然変異)を避けるためにコードを書き直すことができます。 たとえば、 colormap
オブジェクトがあり、 colormap
の値を変更して'blue'
設定する関数を作成するとします。 間違って書いた可能性があります。
function updateColorMap(colormap) { colormap.right = 'blue'; }
元のオブジェクトを変更せずにこれを実装するには、 Object.assignメソッドを使用できます。
function updateColorMap(colormap) { return Object.assign({}, colormap, {right: 'blue'}); }
updateColorMap
は、古いオブジェクトを変更するのではなく、新しいオブジェクトを返すようになりました。 Object.assign
はES6に含まれており、babel-polyfillを含める必要があります。
ES6では、オブジェクトのプロパティを拡張することが可能であるため、突然変異を起こさずにオブジェクトの更新をさらに簡単に実装できます。
function updateColorMap(colormap) { return {...colormap, right: 'blue'}; }
Create React Appを使用してアプリケーションを作成する場合、 Object.assign
およびオブジェクトのObject.assign
構文はデフォルトで利用可能です。
不変のデータ構造を使用する
Immutable.jsは、この問題を解決する別の方法です。 このライブラリは、構造交換を通じて機能する不変で永続的なコレクションを提供します。
- 不変 :一度作成されたコレクションは、別の時点では変更できません。
- 永続的 :以前のコレクションと変更されたデータセットから新しいコレクションを作成できます。 元のコレクションは、新しいコレクションの作成後も有効のままです。
- 構造的交換 :元のコレクションと同じ構造を可能な限り使用して新しいコレクションが作成され、コピーを最小限に抑えてパフォーマンスを向上させます。
不変性は変更の追跡を容易にします。 変更は常に新しいオブジェクトの作成につながり、オブジェクトへのリンクが変更されたかどうかのみを確認できます。 たとえば、ネイティブJavaScriptコードの場合:
const x = { foo: "bar" }; const y = x; y.foo = "baz"; x === y; // true
y
が変更されたという事実にもかかわらず、 y
はx
と同じオブジェクトを参照します-それらの比較はtrue
返しtrue
。 immutable.jsを使用してこのコードを書き換えることができます。
const SomeRecord = Immutable.Record({ foo: null }); const x = new SomeRecord({ foo: 'bar' }); const y = x.set('foo', 'baz'); x === y; // false
この場合、なぜなら x
変更され、新しいオブジェクトへの参照が返された場合、 x
が変更されたと安全に想定できます。
不変データの使用に役立つ他の2つのライブラリーは、 seamless-immutableおよびimmutability-helperです。
不変のデータ構造は、オブジェクトの変更を追跡する最も簡単な方法を提供します。これはshouldComponentUpdate
を使用する必要があるもので、ほとんどの場合、パフォーマンスが大幅に向上します。
前のパーツ: