Reactアプリケヌションを䜜成する際のパフォヌマンスの問題を回避する


反応パフォヌマンスに぀いお


Reactは非垞に生産的なフレヌムワヌクずは考えられたせん。 これにより、倚数の芁玠を持぀高速で動的なペヌゞを䜜成できたす。


しかし、ペヌゞに倚くの芁玠があり、組み蟌みのリアクションパフォヌマンスでは䞍十分な堎合がありたす。 次に、最適化のためのさたざたな手法を適甚する必芁がありたす。


reactで曞かれたペヌゞは、別々のコンポヌネントで構成されおいたす。 それらのそれぞれは、ペヌゞの特定の郚分の倖芳を担圓したす。 この倖芳は、コンポヌネントに枡されたパラメヌタヌプロパティによっお異なりたす。 むベントが発生するずたずえば、ナヌザヌのアクション䞭たたはネットワヌク経由でデヌタを受信䞭、プロパティが倉曎される堎合がありたす。 コンポヌネントのプロパティが倉曎された堎合、これらの倉曎がナヌザヌの画面に衚瀺されるように再描画する必芁がありたす。


むベントが発生するず、それらの原因ずなっおいるコンポヌネントのプロパティが倉曎されおいなくおも、ペヌゞの䞀郚を再描画できたす。 そのようなパヌツが倚数ある堎合、ナヌザヌの操䜜によっお、レンダリングにかなりの時間がかかり、ペヌゞむンタヌフェむスずの察話時に顕著な遅延が発生する可胜性がありたす。 このようなパフォヌマンスの問題に察凊する䞻な方法は、プロパティが倉曎されおいない堎合、コンポヌネントの再描画をキャンセルするこずです。 ぀たり、むベントが発生した堎合、プロパティず倖芳がこのむベントに圱響するペヌゞ䞊に芁玠のみを再描画する必芁がありたす。 これを行うには、Reactコンポヌネントを䜜成するずきにshouldComponentUpdateメ゜ッドを定矩するか、React.Componentの代わりにReact.PureComponentを芪クラスずしお䜿甚する必芁がありたす。


再描画が䞍芁な堎合、shouldComponentUpdateメ゜ッドはfalseを返す必芁がありたす。 たた、React.PureComponentクラスでは、このメ゜ッドは既に実装されおいたす。 すべおの受信プロパティをチェックし、プロパティが倉曎されおいない堎合、コンポヌネントは再描画されたせん。


shouldComponentUpdateの䟋


import * as React from 'react'; class Component extends React.Component { shouldComponentUpdate(nextProps) { return this.props.value !== nextProps.value } render() { return <div>...</div>; } } 

React.PureComponentの䟋


 import * as React from 'react'; class Component extends React.PureComponent { render() { return <div>...</div>; } } 

最適化が機胜しない堎合


shouldCompenentUpdateおよびReact.PureComponentの動䜜原理は、叀いプロパティず新しいプロパティを比范するこずに基づいおいたす。 プロパティが倉曎されおいない堎合、コンポヌネントの倖芳は曎新されたせん。


したがっお、必芁な堎合にのみプロパティを倉曎するようにしおください。
ただし、コンポヌネントが䞍芁な堎合に新しいプロパティをうっかり受け取っおしたう堎合がありたす。


これは、バむンドメ゜ッド、矢印関数、リテラルオブゞェクト、たたはrenderメ゜ッドのすべおの呌び出しで新しいオブゞェクトを䜜成するその他の構造を䜿甚するずきに発生する可胜性がありたす。


最適化が機胜しない堎合


 class ParentComponent extends React.Component { _onClick() { doSomehing(); } render() { return ( <div> <Child1 onClick={this._onClick.bind(this)}/> <Child2 onClick={() => this._onClick()}/> <Child3 data={{id: this.props.id, value: this.props.value}}/> <Child4 items={this.props.items.map((item) => {.....})}/> </div> ); } } 

バむンド関数ず矢印関数は、実行のたびに関数の新しいむンスタンスを返したす。


バむンド実行結果の比范


 this._onClick.bind(this) === this._onClick.bind(this) // false () => this._onClick() === () => this._onClick() //false 

したがっお、ここでは最初の2぀のコンポヌネントが新しいonClickプロパティを毎回受け取りたす。
Child3はdataプロパティで新しいオブゞェクトを受け取り、4番目はアむテムで新しい配列を受け取りたす。


このような構造は避けおください。 これらを䜿甚する堎合、shouldComponentUpdateたたはReact.PureComponentを䜿甚しお実行されるパフォヌマンスの最適化は、新しいプロパティのために機胜したせん。


原則ずしお、このようなshouldComponentUpdateの実装を蚘述できたす。この実装では、䞀郚のプロパティぞの倉曎は無芖され、曎新は行われたせん。 ただし、この方法はすべおの状況で䜿甚できるわけではありたせん。 たず、倚くのプロパティがある堎合、そのような実装にはかなり倚数のチェックが含たれるこずがあり、これが゚ラヌやバグの原因になる可胜性がありたす。 たた、新しいプロパティを远加する堎合は、shouldComponentUpdateメ゜ッドをファむナラむズする必芁があるため、このコヌドの保守はより困難です。 次に、このアプロヌチでは、shouldComponentUpdateが実装されおいるコンポヌネントは、どのプロパティを無芖できるかを正確に刀断するために、䜿甚する堎所ず状況を正確に把握する必芁がありたす。 そしお、それが䜿甚される堎所では、それらのプロパティに新しい倀を転送しないように内郚実装を知る必芁がありたす。その倉曎は䞍必芁な再描画に぀ながりたす。 これは、コンポヌネントのカプセル化を倧きく損ない、これはほずんどの堎合望たしくありたせん。


性胜詊隓結果


倚くの堎合、コヌドの蚘述時およびコヌドのレビュヌ時に、時間を費やしおそのような問題に泚意を払うべきかどうかに぀いお疑問が生じたす。 新しいプロパティをコンポヌネントに枡すこずがパフォヌマンスに䞎える圱響を刀断するために、小さなテストペヌゞが䜜成されたした。 https://megazazik.imtqy.com/react-perf-test/にありたす


このプロゞェクトを䜿甚するず、新しいプロパティを受け取るたびに、たたプロパティが倉曎されおいない堎合に、倚数の小さなコンポヌネントの曎新時間を枬定できたす。 テストには、さたざたなコンポヌネントが䜿甚されたした。



プロゞェクトペヌゞを開くず、数千の芁玠のリストが衚瀺されたす。 アむテムをクリックするかホバヌするず、遞択したアむテムがアクティブになり、リストが再描画されたす。 同時に、遞択したパラメヌタヌに応じお、状態が倉曎されおいない芁玠は、矢印関数の䜿甚により同じプロパティたたは新しいプロパティを受け取りたす。


1000アむテムの曎新時間


デバむス、ブラりザ最適化ピュア、mcステヌトフル、mcステヌトレス、mchtml、mc
デスクトップ、Chromeいや6.56.35.83,5
デスクトップ、Chromeはい3.34.94.32,5
デスクトップ、Firefoxいや13.711.911.57.3
デスクトップ、Firefoxはい5,48.57.64.4
デスクトップ、゚ッゞいや19.616,213.58.6
デスクトップ、゚ッゞはい8.111.58.74.7
モバむル、Chromeいや31.130,229.619.3
モバむル、Chromeはい16.725.821.514.8

特定の数倀は、ペヌゞを開くデバむスずブラりザヌ、メモリの状態、プロセッサの負荷によっお倧きく異なる堎合がありたす。 ただし、最適化ありず最適化なしのレンダリングパフォヌマンスの盞察的な違いは、垞にほが同じレベルに維持する必芁がありたす。


結果をデバむスに関連する結果ず比范できたす。


最適なレンダリング時間は、デバむス画面の曎新時にブラりザがフレヌムをスキップしないような時間ず芋なすこずができたす。 最新の画面のほずんどは、少なくずも60フレヌム/秒のリフレッシュレヌトをサポヌトしおいたす。 画面は少なくずも16.6ミリ秒ごずに曎新されるこずがわかりたす。 したがっお、远加の負荷の可胜性を考慮しお、すべおのペヌゞコンポヌネントの曎新が10ミリ秒以内に行われるこずが望たしいです。


テスト結果は、プロパティが倉曎されない堎合、PureComponentおよびhtml芁玠から継承されたコンポヌネントのレンダリング時間が倧幅に短瞮されるこずを瀺しおいたす。
テストでは、プロパティの䞍倉性は他のタむプのコンポヌネントのパフォヌマンスにも圱響したす。 しかし、それは非垞に小さく、再描画のキャンセルずは関係ありたせんが、受け取ったプロパティは最終的にマヌクアップ内の同じdiv芁玠に転送され、すでに最適化されおいたす。


テスト䞭、最小のコンポヌネントが䜿甚されたした。 サむズが倧きくなるず、生産性の向䞊は増加するだけです。 この効果は、テストプロゞェクトで[リストの砎損]オプションが有効になっおいる堎合、倧たかに掚定できたす。 このモヌドでは、リスト党䜓がそれぞれ10個の芁玠のグルヌプに分割されたす。 次に、10グルヌプごずに䞊䜍レベルのグルヌプに順番に結合されたす。 その結果、ペヌゞは10個のサブグルヌプを含む10個のグルヌプで構成され、各サブグルヌプには10個の芁玠がありたす。


この内蚳は、実際のペヌゞの構成に䌌おいたす。 通垞、ペヌゞには、小さなネストされたコンポヌネントを含むいく぀かの倧きなコンポヌネントが含たれたす。


このモヌドでは、最悪の状況、むベントの発生時にペヌゞのすべおの芁玠が再描画される状況、および状況の間のパフォヌマンスの違いを評䟡できたす。 プロパティが倉曎された芁玠のみが曎新される堎合。


デバむス、ブラりザ最適化ピュア、mcステヌトフル、mcステヌトレス、mchtml、mc
デスクトップ、Chromeいや8.78.38.06.1
デスクトップ、Chromeはい0.90.90.90.8
デスクトップ、Firefoxいや19.116.315.412.6
デスクトップ、Firefoxはい1,51,51,51.3
デスクトップ、゚ッゞいや23.120.618.113.0
デスクトップ、゚ッゞはい2.92.92.92,8
モバむル、Chromeいや37.135.034.524.9
モバむル、Chromeはい5.25,65.34.7

この堎合、リストが分割されるグルヌプには独自のshouldComponentUpdateメ゜ッドが実装されおいるため、テストするコンポヌネントのタむプは重芁ではありたせん。


レンダリングで新しい関数を䜜成しないようにする方法


実際には、ほずんどの堎合、新しいコンポヌネントプロパティの䞍必芁な生成は、矢印関数たたはバむンド関数の䜿甚によるものです。 この問題を解決する方法を怜蚎しおください。 ほずんどの堎合、これは非垞に簡単です。 しかし、時には時間がかかりたす。


曎新ごずに新しい関数を䜜成しない最も簡単な方法は、コンポヌネントむンスタンスが初期化されるずきに䞀床䜜成するこずです。


これを行うには、renderメ゜ッドではなくクラスのコンストラクタヌでbind関数を呌び出し、埌でrenderメ゜ッドで参照するために、結果をオブゞェクトのプロパティに保存したす。


コンストラクタヌでのバむンドの䟋


 class ParentComponent extends React.Component { constructor(props) { super(props); this._onClick = this._onClick.bind(this); } _onClick() { doSomehing(); } render() { return <ChildComponent onClick={this._onClick}/>; } } 

たたは、矢印関数を䜿甚しお、結果をオブゞェクトのプロパティに保存するこずもできたす。


矢印関数を䜿甚した䟋


 class ParentComponent extends React.Component { _onClick = () => { doSomehing(); } render() { return <ChildComponent onClick={this._onClick}/>; } } 

前の方法は、簡単か぀迅速に実装できたす。 ほずんどの堎合、䜿甚する䟡倀がありたす。


ただし、ルヌプ内など、コヌルバック関数が子コンポヌネントのリストに枡され、実行されたずきに、呌び出しが発生した芁玠を知る必芁がある堎合がありたす。 このような関数は、たずえば、配列芁玠のむンデックスを取るこずができたす。


リストの䟋


 class ParentComponent extends React.Component { _onClick = (index) => { doSomehing(index); } render() { return ( <div> {this.props.items.map((item, index) => ( <ChildComponent onClick={() => this._onClick(index)} item={item} /> ))} </div> ); } } class ChildComponent extends React.PureComponent { render() { return <div onClick={this.props.onClick}>...</div>; } } 

ここで、リストの各子コンポヌネントは、独自の䞀意の関数を受け取る必芁がありたす。この関数では、芁玠のむンデックスがクロヌゞャヌを通じお保存されたす。


この堎合、各レンダリングで新しい関数の䜜成を取り陀くこずはより困難です。 これを行うにはいく぀かの方法がありたす。


子コンポヌネントのむンタヌフェヌスを倉曎する


最初のアプロヌチを適甚するには、コヌルバック関数を1぀だけ䜜成し、それをすべおの子コンポヌネントに枡す必芁がありたす。 この堎合、必芁な匕数を関数に枡す責任は子コンポヌネントにありたす。


idを枡す䟋


 class ParentComponent extends React.Component { _onClick = (id) => { doSomehing(id); } render() { return ( <div> {this.props.items.map((item) => ( <ChildComponent onClick={this._onClick} item={item} /> ))} </div> ); } } class ChildComponent extends React.PureComponent { _onClick = () => { this.props.onClick(this.props.item.id) } render() { return <div onClick={this._onClick}>...</div>; } } 

子コンポヌネント内にコヌルバック関数に必芁なデヌタがない堎合、プロパティを介しお子コンポヌネントに転送する必芁がありたす。


プロパティにむンデックスを远加する䟋


 class ParentComponent extends React.Component { _onClick = (index) => { doSomehing(index); } render() { return ( <div> {this.props.items.map((item, index) => ( <ChildComponent onClick={this._onClick} item={item} index={index} /> ))} </div> ); } } class ChildComponent extends React.PureComponent { _onClick = () => { this.props.onClick(this.props.index) } render() { return <div onClick={this._onClick}>...</div>; } } 

この方法は垞に䜿甚できるずは限りたせん。 たず、子コンポヌネントの゜ヌスコヌドにアクセスできない堎合がありたす。 第二に、新しいプロパティを远加するこずは、むンタヌフェヌスの明瞭さず敎合性に悪圱響を䞎える可胜性があるため、望たしくない堎合がありたす。


コンポヌネントのハむラむト


前の方法を䜿甚できない堎合、たたは望たしくない堎合は、コヌルバック関数に远加のパラメヌタヌを枡す責任がある別のコンポヌネントを䜜成できたす。


コンポヌネント割り圓おの䟋


 class ParentComponent extends React.Component { _onClick = (index) => { doSomehing(index); } render() { return ( <div> {this.props.items.map((item, index) => ( <ChildWrapperComponent onClick={this._onClick} item={item} index={index} /> ))} </div> ); } } class ChildWrapperComponent extends React.PureComponent { _onClick = () => { this.props.onClick(this.props.index) } render() { return ( <ChildComponent onClick={this._onClick} item={item} /> ); } } 

この方法は、ほずんどすべおの状況で䜿甚できたす。 おそらく圌の唯䞀の欠点は、それが比范的面倒であるこずです。 renderを呌び出すずきに関数を䜜成するのをやめるためだけに、別のクラスを曞く必芁がありたす。


プロパティずしお倀をネむティブ芁玠に枡す


配列むンデックスたたは他のキヌを子html芁玠ボタンや入力などに転送するメ゜ッドを䜿甚するこずもできたす。コヌルバック関数でむベントが発生するず、このデヌタはhtml芁玠から抜出されたす。


html芁玠を䜿甚した䟋


 class ParentComponent extends React.Component { _onClick = (e) => { doSomehing(e.target.value); } render() { return ( <div> {this.props.items.map((item, index) => ( <button type="button" onClick={this._onClick} value={index} > {item.title} </button> ))} </div> ); } } 

html芁玠の堎合、shouldComponentUpdateを実装できたせんが、テストで瀺されおいるように、組み蟌みの最適化メカニズムがあり、プロパティの䞍倉性により、そのような芁玠の曎新時間が倧幅に短瞮されたす。


コヌルバック関数のキャッシュ


このメ゜ッドを䜿甚するには、すべおの子コンポヌネントのコヌルバック関数のリストを䞀床䜜成し、その埌のレンダリングに以前に䜜成した関数を䜿甚する必芁がありたす。 そのような実装のオプションの1぀


コヌルバックキャッシング


 class ParentComponent extends React.Component { _callbacks = {}; _getOnClick = (index) => { if (!this._callbacks[index]) { this._callbacks[index] = () => doSomehing(index); } return this._callbacks[index]; } render() { return ( <div> {this.props.items.map((item, index) => ( <ChildComponent onClick={this._getOnClick(index)} item={item} /> ))} </div> ); } } 

この方法は比范的時間がかかりたす。 むンデックスに加えお、コヌルバック関数に他のデヌタを転送する必芁がある堎合、それが正しく機胜するためには、より耇雑なロゞックを実装し、より倚くのコヌドを蚘述する必芁がありたす。 さらに、この関数は必芁に応じお各コンポヌネントにコピヌする必芁があり、このコヌドを同じクラスに数回埋め蟌むこずができたす。 したがっお、各クラスのコヌドの重耇を避けるために、必芁なアクションを実行するモゞュヌルを䜜成できたす。


そのようなモゞュヌルの䟋ずしお、2぀のパッケヌゞがありたす。



どちらのパッケヌゞにも同じ機胜がありたすが、むンタヌフェヌスは異なりたす。
これらを䜿甚するず、リストから各子コンポヌネントのコヌルバック関数を䞀床䜜成し、それを以降のレンダリングごずに䜿甚できたす。


cached-bindの䜿甚䟋


 import bind from 'cached-bind'; class ParentComponent extends React.Component { _onClick(index) { doSomehing(index); } render() { return ( <div> {this.props.items.map((item, index) => ( <ChildComponent onClick={bind(this, '_onClick', index)} item={item} /> ))} </div> ); } } 

React-cached-callbackの䟋


 import cached from 'react-cached-callback'; class ParentComponent extends React.Component { @cached _getOnClick(index) { return () => doSomehing(index); } render() { return ( <div> {this.props.items.map((item, index) => ( <ChildComponent onClick={this._getOnClick(index)} item={item} /> ))} </div> ); } } 

どのストアド関数を返す必芁があるかを理解するには、䞡方のパッケヌゞが呌び出されたずきに子コンポヌネントの識別子を決定する必芁がありたす。 識別子ずしお、たずえば、配列内の芁玠のむンデックスを䜿甚できたす。


cached-bindは、バむンド関数を呌び出すずきに、この識別子を3番目の匕数ずしお受け取る必芁がありたす。
たた、react-cached-callbackはデフォルトで、元の関数に枡された最初の匕数を識別子ず芋なしたす。 むンタヌフェヌスの詳现は、パッケヌゞの説明に蚘茉されおいたす。


結論ずしお


Reactは高速なフレヌムワヌクであり、アプリケヌションの倧郚分は远加の劎力なしで十分なパフォヌマンスで動䜜したす。 ただし、アプリケヌションのサむズが倧きくなるず、開発者はさたざたな最適化手法を適甚し、コンポヌネントのプロパティが䞍必芁に倉曎されないようにする必芁がありたす。


珟圚、倚くの状態管理ラむブラリreduxやMobXなどには、反応コンポヌネントを最適化する独自の方法がありたす。 ただし、コンポヌネントを䜿甚する堎合でも、コンポヌネントをレンダリングするずきにオプションで新しいオブゞェクトず関数を生成するず、ナヌザヌむンタヌフェむスの曎新が倧幅に遅れるこずがありたす。



Source: https://habr.com/ru/post/J348584/


All Articles