Reactのパフォーマンス最適化

反応する高度なチュートリアル。パート5







公式のReact.jsライブラリドキュメントのAdvanced Guidesセクションの一連の翻訳の継続。







Reactのパフォーマンス最適化



内部的に、Reactは、ユーザーインターフェイスの更新に必要なコストのかかるDOM操作の数を最小限に抑えるいくつかの高度な手法を使用しています。 Reactを使用するほとんどのアプリケーションでは、パフォーマンスを最適化するための追加手順なしで、結果のインターフェイスの速度で十分です。 ただし、Reactアプリケーションを高速化する方法はいくつかあります。









最終(生産)アセンブリを使用する



Reactアプリケーションでパフォーマンスをテストしている場合、またはパフォーマンスの問題が発生している場合は、縮小された実動アセンブリーを必ずテストしてください。









 new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify('production') } }), new webpack.optimize.UglifyJsPlugin()
      
      





開発用アセンブリには、アプリケーションの開発に役立つ追加の警告が含まれていますが、追加のアクションを実行することでアプリケーションの速度が低下します。







Chromeのタイムラインを使用したコンポーネントのプロファイリング



サポートされているブラウザーの開発ツールでパフォーマンスツールを使用すると、コンポーネントのマウント、更新、アンマウントの方法を視覚化できます。 例:







タイムラインChromeのReactコンポーネント







Chromeで次の手順を実行します。







  1. クエリ文字列に?react_perf



    パラメーターを指定してアプリケーションをダウンロードします(例: http://localhost:3000/?react_perf



    )。







  2. Chromeデベロッパーツールの[ タイムライン ]タブをクリックし、[ 記録 ]をクリックします。







  3. プロファイリングする手順に従います。 20秒以上アクションを記録しないでください。そうしないと、Chromeがフリーズする可能性があります。







  4. 記録を停止します。







  5. 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



を使用する必要があるもので、ほとんどの場合、パフォーマンスが大幅に向上します。







前のパーツ:









出典: React-上級ガイド-パフォーマンスの最適化








All Articles