この質問はいつも出てくるわけではありませんでしたが、ここ数か月間、図書館の作者と教師として、ほぼ毎日、時には講義で、時にはTwitterで答えなければなりません。 正直なところ、私はすでにこれにうんざりしています。 残念ながら、すべてを記事の形にする方が良いとすぐに気づきませんでした。これは、パフォーマンスの質問をしている人に役立つと思います。 実際、あなたの前には私の労苦の成果があります。
組み込み関数とは何ですか?
Reactのコンテキストでは、インライン関数と呼ばれるものは、レンダリングプロセス中に定義される関数です。 Reactでは、「レンダリング」の概念には2つの意味があり、しばしば混同されます。 1つ目は、アップグレードプロセス中にコンポーネントからReact要素を取得
render
(コンポーネント
render
メソッドを呼び出す)ことに関するものです。 2番目は、DOMを変更することによるページフラグメントの実際の更新です。 この記事で「レンダリング」について話すとき、私は最初の選択肢を意味します。
組み込み関数の例を次に示します。
class App extends Component { // ... render() { return ( <div> {/* 1. " DOM" */} <button onClick={() => { this.setState({ clicked: true }) }} > Click! </button> {/* 2. " " "" */} <Sidebar onToggle={(isOpen) => { this.setState({ sidebarIsOpen: isOpen }) }}/> {/* 3. render */} <Route path="/topic/:id" render={({ match }) => ( <div> <h1>{match.params.id}</h1>} </div> ) /> </div> ) } }
早すぎる最適化はすべての悪の根源です
続行する前に、プログラムを最適化する方法について話す必要があります。 パフォーマンスの専門家に尋ねると、彼は時期尚早な最適化は悪であると言うでしょう。 これは、絶対にすべてのプログラムに適用されます。 最適化についてよく知っている人なら誰でもこれを確認できます。
私の友人である
gzip
に捧げられたRalph Holzmannのスピーチを覚えています。 彼は、スクリプトをロードするための古いライブラリである
LABjs
で行った実験について話しました。 このパフォーマンスを見ることができます 。 私がここで話していることは、ビデオの30分から始めて約2分半かかります。
当時のLABjsでは、完成したコードのサイズを最適化することを目的とした奇妙なことが行われました。
obj.foo
は、通常のオブジェクト表記(
obj.foo
)を使用する代わりに、キーを文字列に格納し、角括弧を使用してオブジェクトのコンテンツ(
obj[stringForFoo]
)にアクセスしました。 これは、
gzip
コードを縮小および圧縮した後、通常とは異なる方法で作成されたコードが、通常の方法で作成されたコードよりも小さくなるはずだったためです。
Ralphはこのコードを分岐して最適化を削除し、縮小とgzip圧縮のためにコードを最適化する方法を考えずに、通常の方法で書き換えました。
「最適化」を取り除くことで、結果のファイルのサイズを5.3%削減できることがわかりました。 明らかに、ライブラリの作成者は、利点を与えるかどうかをチェックせずに、「最適化された」形式ですぐにそれを書きました。 特定の最適化によって何かが改善されるかどうかを測定することは不可能です。 さらに、最適化によって状況が悪化するだけの場合、これについてもわかりません。
時期尚早な最適化は、開発時間を大幅に増加させ、コードの純度を悪化させるだけでなく、LABjsの場合のように、マイナスの結果をもたらし、問題を引き起こす可能性があります。 ライブラリの作成者がパフォーマンスの問題を想像する代わりに測定を行った場合、開発時間を節約し、より優れた特性を備えたよりクリーンなコードをリリースします。
ここでこのツイートを引用します。「アームチェアでくつろいでいる人が、パフォーマンスの測定をせずに問題を解決するのに時間がかかるコードがあると主張するのはいらいらします。」 私はこの観点を支持します。
だから、私は繰り返します-時期尚早な最適化を行いません。 さて、Reactに戻ります。
組み込み関数がパフォーマンスを低下させると言われるのはなぜですか?
組み込み関数は、2つの理由で遅いと見なされます。 まず、これはメモリ消費とガベージコレクションに関する懸念によるものです。 第二に
shouldComponentUpdate
です。 これらの懸念を調べてみましょう。
▍メモリ消費とガベージコレクション
まず、プログラマー(およびestlint構成 )は、 組み込み関数を作成する際のメモリー消費とガベージコレクションからのシステム負荷を懸念します。 これは、JSの矢印関数がまだ広く使用されていなかった時代の遺産です。 ビルトインコンストラクトのReactコードで
bind
コマンドが頻繁に使用された場合、これは歴史的にパフォーマンスの低下につながりました。 例:
<div> {stuff.map(function(thing) { <div>{thing.whatever}</div> }.bind(this)} </div>
Function.prototype.bind
の問題はここで修正され 、矢印関数は言語の組み込み機能として使用されるか、またはbabelを使用して通常の関数に変換されました。 そして、そういうわけで、私たちはそれらが遅いわけではないと仮定することができます。
一部のコードが遅くなるという前提を立てる必要はありません。 いつものようにコードを書き、パフォーマンスを測定します。 問題が見つかった場合は、修正してください。 矢印関数が高速に動作することを証明する必要はありません。他の人にそれらが遅いことを証明させてください。 それ以外の場合は、時期尚早な最適化です。
私の知る限り、誰もアプリケーションの研究を行っていません。組み込み機能がパフォーマンスの問題につながることを示しています。 ここまでは、これについて話す必要はありませんが、いずれにしても、ここで別のアイデアを共有します。
システムの組み込み関数の作成による負荷がこれを防ぐ特別なエスリントルールを作成するのに十分高い場合、なぜこれらの重い操作をシステムの速度に影響を与えるという点で非常に重要な初期化ユニットに移動しようとするのでしょうか?
class Dashboard extends Component { state = { handlingThings: false } constructor(props) { super(props) this.handleThings = () => this.setState({ handlingThings: true }) this.handleStuff = () => { /* ... */ } // bind this.handleMoreStuff = this.handleMoreStuff.bind(this) } handleMoreStuff() { /* ... */ } render() { return ( <div> {this.state.handlingThings ? ( <div> <button onClick={this.handleStuff}/> <button onClick={this.handleMoreStuff}/> </div> ) : ( <button onClick={this.handleThings}/> )} </div> ) } }
予備的な最適化では、コンポーネントの初期化を3回遅くしました。 すべてのイベントハンドラーが組み込み関数であった場合、
render
の最初の呼び出しで必要な関数は1つだけです。 代わりに、3つ作成します。 さらに、パフォーマンス測定は行われなかったため、これを問題と見なす理由はありません。
ただし、必要なものと不要なものをすべて組み込み関数に転送するという考えに夢中になるべきではありません。 上記のアイデアに触発されて、誰かが初期のレンダリングを高速化するために組み込み関数の広範な使用を必要とするeslintルールを作成することに決めた場合、同じ有害な時期尚早な最適化に直面します。
urePureComponentおよびshouldComponentUpdate
問題の真の核は
PureComponent
と
shouldComponentUpdate
ます。 パフォーマンスを最適化するためには、2つのことを理解する必要があります
shouldComponentUpdate
の機能と、JavaScriptでの厳密な同等性の比較の仕組みです。 これらの概念を理解しなくても、コードの高速化を試みても事態は悪化するだけです。
setState
が
setState
、Reactは古い要素と新しい要素を比較し(これを調整と呼びます)、受け取った情報を使用して実際のDOMの要素を更新します。 チェックする必要のある要素が多すぎる場合(大きなSVGのようなもの)、この操作はかなり遅いことがあります。 このような場合、Reactは
shouldComponentUpdate
という回避策を提供します。
class Avatar extends Component { shouldComponentUpdate(nextProps, nextState) { return stuffChanged(this, nextProps, nextState)) } render() { return //... } }
Reactが古い要素と新しい要素を比較する前に、コンポーネント
shouldComponentUpdate
設定されている場合、
shouldComponentUpdate
を
shouldComponentUpdate
、何かが変更されたかどうかを確認します。 応答が
false
返した場合、Reactは要素比較操作を完全にスキップするため、時間を節約できます。 コンポーネントが十分に大きい場合、これはパフォーマンスに顕著な影響をもたらす可能性があります。
コンポーネントを最適化する最も一般的な方法は、
React.PureComponent
ではなく
React.PureComponent
を拡張すること
React.Component
。
PureComponent
は
PureComponent
プロパティと状態を比較します。その結果、自分で行う必要はありません。
class Avatar extends React.PureComponent { ... }
Avatar
クラスは、更新を要求する前にプロパティと状態を操作するときに、厳密な等価比較を使用するようになりました。 これにより、プログラムが高速化されることが期待できます。
厳密な平等の比較
JavaScriptには、
string
、
number
、
boolean
、
null
、
undefined
、
symbol
6つのプリミティブ型があります。 同じ値を格納するプリミティブ型の2つの変数の厳密な比較が実行されると、
true
取得されます。 例:
const one = 1 const uno = 1 one === uno // true
PureComponent
プロパティを比較するとき、厳密な比較を使用します。 これは、
<Toggler isOpen={true}/>
ような埋め込みプリミティブ値に最適です。
プロパティを比較するときの問題は、他のタイプ、つまり申し訳ありません-唯一のタイプで発生します。 JSの他のすべては
Object
です。 しかし、関数と配列はどうでしょうか? 実際、これらはすべてオブジェクトです。 「関数は、実行のために呼び出される追加の機能を持つ通常のオブジェクトです」というMDNドキュメントからの抜粋を引用できます。
まあ私は何を言うことができます-JSはJSです。 いずれの場合でも、異なるオブジェクトの厳密な比較は、たとえ同じ値が含まれていても、
false
を返し
false
。
const one = { n: 1 } const uno = { n: 1 } one === uno // false one === one // true
そのため、オブジェクトをJSXコードに埋め込むと、
PureComponent
のプロパティの適切な比較が不可能になり、その結果、React要素のより時間のかかる比較が行われます。 この比較では、結果としてコンポーネントが変更されていないことがわかります-2つの比較での時間の損失。
// <Avatar user={{ id: 'ryan' }}/> // <Avatar user={{ id: 'ryan' }}/> // , - , {} !== {} // () ,
関数はオブジェクトであり、
PureComponent
はプロパティの等価性を厳密にチェックするため、プロパティの分析時に組み込み関数の比較は常に、それらが異なるというメッセージで終了します。その後、マッチング手順中の要素の比較への移行が実行されます。
これは組み込み関数だけに適用されるわけではないことに気付くかもしれません。 通常のオブジェクトや配列についても同じことが言えます。
shouldComponentUpdate
同一の関数を比較するときに期待することを行うには、関数の参照IDを維持する必要があります。 経験豊富なJS開発者にとって、これはそれほど悪いニュースではありません。 しかし、 Michaelと私がさまざまなレベルのトレーニングを受けた約3,500人のトレーニング後に学んだことを考慮すると、このタスクは学生にとってそれほど単純ではなかったことがわかります。 ESクラスはここでも役に立たないため、この状況では他のJS機能を使用する必要があることに注意してください。
class Dashboard extends Component { constructor(props) { super(props) // bind? , 20, // . // , . this.handleStuff = this.handleStuff.bind(this) // _this - . var _this = this this.handleStuff = function() { _this.setState({}) } // ES, , , // ( , babel ). // , - // . this.handleStuff = () => { this.setState({}) } } // , JavaScript, // , TC39 // . handleStuff = () => {} }
関数の参照IDを保存するための手法の研究は、驚くほど長い会話につながることに注意する必要があります。 eslint構成の要件を満たしたい場合を除き、プログラマーに電話する理由はありません。 私が見せたかった主なことは、組み込み関数が最適化に干渉しないことです。 次に、パフォーマンスの最適化に関する独自のストーリーを共有します。
PureComponentの使用方法
PureRenderMixin
(これは以前のバージョンのReactのデザインで、後に
PureComponent
になったデザイン)を初めて知ったとき、多くの次元を使用して、アプリケーションのパフォーマンスを評価しました。 次に、
PureRenderMixin
をすべてのコンポーネントに追加しました。 最適化されたバージョンのパフォーマンス測定を行ったとき、結果としてすべてが素晴らしくなり、誇らしげにみんなにそれを伝えることができることを望みました。
しかし、驚いたことに、アプリケーションの動作が遅くなりました。
なんで? 考えてみてください。 特定の
Component
がある場合、その
Component
操作する際に実行する必要がある比較操作の数はいくつですか?
PureComponent
どうですか? それぞれの答えは、「1つだけ」、「少なくとも1つ、時には2つ」です。 アップグレード中にコンポーネントが通常変更される場合、
PureComponent
は1つではなく2つの比較操作を実行します(
shouldComponentUpdate
プロパティと状態、および通常の要素の比較)。 これは、通常、
PureComponen
tが遅くなることを意味しますが、時には速くなります。 明らかに、私のコンポーネントのほとんどは絶えず変化していたため、一般に、アプリケーションの動作が遅くなりました。 悲しいです
「生産性を高める方法」という質問に対する普遍的な答えはありません。 答えは、特定のアプリケーションのパフォーマンス測定でのみ見つけることができます。
3つの組み込み関数の使用シナリオについて
素材の冒頭で、3種類の組み込み関数を示しました。 いくつかのベースが準備されたので、それぞれについて話しましょう。 ただし、このメカニズムを使用する利点を評価するために、測定するまで
PureComponent
を保持することをお勧めします。
▍DOMコンポーネントイベントハンドラー
<button onClick={() => this.setState(…)} >click</button>
通常、ボタン、入力フィールド、または
setState
呼び出し以外の他のDOMコンポーネントのイベントハンドラー内では何も行われません。 これにより、通常、インライン関数が最もクリーンなアプローチになります。 イベントハンドラの検索でファイルをジャンプする代わりに、それらは要素記述コードで見つけることができます。 通常、Reactコミュニティはこれを歓迎します。
button
コンポーネント(および他のDOMコンポーネント)を
PureComponent
にすることさえできないため、
shouldComponentUpdate
および参照IDについて心配する必要はありません。
結果として、関数の単純な定義がシステムのかなり大きな負荷であり、心配する価値があることに同意する場合にのみ、これは遅いと考えることができます。 これがそうであるという証拠はありません。 組み込みのイベントハンドラーの不当な廃棄は、よく知られている時期尚早な最適化です。
▍「カスタムイベント」または「アクション」
<Sidebar onToggle={(isOpen) => { this.setState({ sidebarIsOpen: isOpen }) }}/>
Sidebar —
PureComponent
の場合、プロパティの比較は渡しません。 繰り返しになりますが、ハンドラーは単純であるため、それを埋め込むことが最善の解決策になる可能性があります。
次に、
onToggle
などのイベントと、
Sidebar
それらを比較する理由について説明します。
shouldComponentUpdate
プロパティの違いを探す理由は2つしかありません。
- プロパティはレンダリングに使用されます。
- このプロパティは、
componentWillReceiveProps
、componentDidUpdate
、またはcomponentWillReceiveProps
で副作用を達成するために使用されcomponentWillUpdate
。
ほとんどの
on<whatever>
プロパティはこれらの要件を満たしていません。 したがって、ほとんどの
PureComponent
使用
PureComponent
、不必要な比較が行われ、開発者は参照IDハンドラーを不必要に維持する必要があります。
変更できるプロパティのみを比較する必要があります。 したがって、ハンドラーは要素記述コードに含めることができ、これはすべて迅速に機能します。パフォーマンスが懸念される場合、このアプローチでは比較が少なくて済むことに注意してください。
ほとんどのコンポーネントについては、
PureComponentMinusHandlers
から継承するのではなく、
PureComponentMinusHandlers
クラスを作成して継承することをお
PureComponent
ます。 これは、すべての機能チェックをスキップするのに役立ちます。 必要なものだけ。 まあ-ほとんど必要なもの。
関数を取得し、その関数を別のコンポーネントに直接渡す場合、廃止されます。 これを見てください:
// 1. . // 2. , // , . // 3. setState // ** . // 4. , // . // 5. // , // . class App extends React.Component { state = { val: "one" } componentDidMount() { this.setState({ val: "two" }) } render() { return <Form value={this.state.val} /> } } const Form = props => ( <Button onClick={() => { submit(props.value) }} /> ) class Button extends React.Component { shouldComponentUpdate() { // , , . return false } handleClick = () => this.props.onClick() render() { return ( <div> <button onClick={this.props.onClick}>This one is stale</button> <button onClick={() => this.props.onClick()}>This one works</button> <button onClick={this.handleClick}>This one works too</button> </div> ) } }
→ ここで 、このコードを試すことができます。
したがって、
PureRenderWithoutHandlers
から継承するというアイデアが好きな場合は、比較に関係しないハンドラーを他のコンポーネントに直接渡さないでください。何らかの方法でそれらをラップする必要があります。
ここで、参照IDを維持するか、参照IDを回避する必要があります! パフォーマンスの最適化へようこそ。 少なくとも、このアプローチでは、最適化されたコンポーネントに負担がかかり、それを使用するコードには負担がかかりません。
このサンプルアプリケーションは、 Andrew Clarkによって公開された後に作成した資料に追加されたものであると正直に言わなければなりません。 したがって、参照整合性を維持するタイミングと維持しないタイミングを正確に知っているように思えるかもしれません。
▍レンダリングプロパティ
<Route path="/topic/:id" render={({ match }) => ( <div> <h1>{match.params.id}</h1>} </div> ) />
render —
プロパティ
render —
、共有状態を作成および維持するために存在するコンポーネントを作成するために使用されるテンプレートです ( ここで詳細を確認できます )。
render
プロパティの内容は、コンポーネントに
render
。 例:
const App = (props) => ( <div> <h1>Welcome, {props.name}</h1> <Route path="/" render={() => ( <div> {/* props.name Route , Route PureComponent, , . */} <h1>Hey, {props.name}, let's get started!</h1> </div> )}/> </div> )
,
render
shouldComponentUpdate
. ,
PureComponent
.
, ,
render
. — , .
まとめ
, , , . , , , .
親愛なる読者! React-?