JavaScriptでオブジェクトを操作する機能
まったく同じように見えるいくつかの関数を作成して比較しようとすると、システムの観点からは異なることがわかります。 これを確認するには、次のコードを実行できます。
const functionOne = function() { alert('Hello world!'); }; const functionTwo = function() { alert('Hello world!'); }; functionOne === functionTwo; // false
次に、別の変数に既に割り当てられている既存の関数に変数を割り当てて、これら2つの変数を比較してみましょう。
const functionThree = function() { alert('Hello world!'); }; const functionFour = functionThree; functionThree === functionFour; // true
ご覧のとおり、このアプローチでは、厳密な等価演算子は
true
返し
true
。
オブジェクトは自然に同じように振る舞います:
const object1 = {}; const object2 = {}; const object3 = object1; object1 === object2; // false object1 === object3; // true
ここではJavaScriptについて説明していますが、他の言語で開発した経験がある場合は、ポインターの概念に精通しているかもしれません。 上記のコードでは、オブジェクトが作成されるたびに、システムメモリの一部がそのオブジェクトに割り当てられます。
object1 = {}
形式のコマンドを使用すると、
object1 = {}
専用に割り当てられたメモリの一部が何らかのデータで
object1
ます。
object1
を、オブジェクトに関連するデータ構造がメモリ内にあるアドレスとして想像することは非常に可能です。 コマンド
object2 = {}
実行すると、特に
object2
専用の別のメモリ領域が割り当てられます。
obect1
と
obect1
は同じメモリ領域にありますか? いいえ、それぞれに独自のプロットがあります。 そのため、
object1
と
object1
を比較しようとすると
false
になり
false
。 これらのオブジェクトは同じ構造を持つ場合がありますが、それらが配置されているメモリ内のアドレスは異なり、比較中にチェックされるのはアドレスです。
コマンド
object3 = object1
実行することにより、
object1
のアドレスを
object3
定数に書き込みます。 これは新しいオブジェクトではありません。 この定数には、既存のオブジェクトのアドレスが割り当てられます。 これは次の方法で確認できます。
const object1 = { x: true }; const object3 = object1; object3.x = false; object1.x; // false
この例では、オブジェクトがメモリ内に作成され、そのアドレスが定数
object1
書き込まれます。 次に、同じアドレスが定数
object3
書き込まれます。
object3
変更すると、メモリ内のオブジェクトが変更されます。 これは、たとえば
object1
に格納されているオブジェクトなど、他の参照を使用してオブジェクトにアクセスする場合、変更されたバージョンで既に作業することを
object1
ます。
関数、オブジェクト、React
初心者の開発者が上記のメカニズムを誤解すると、多くの場合エラーが発生します。おそらく、オブジェクトを操作する機能を検討することは別の記事に値します。 ただし、今日のトピックはReactアプリケーションのパフォーマンスです。 この領域では、JavaScript変数と定数がオブジェクト自体に保存されず、それらへのリンクのみであるという事実によってReactアプリケーションがどのように影響を受けるかに注意を払っていない、かなり経験豊富な開発者でも間違いを犯す可能性があります。
これはReactと何の関係がありますか? Reactには、アプリケーションのパフォーマンスを向上させることを目的としたシステムリソースを節約するためのインテリジェントなメカニズムがあります。コンポーネントのプロパティと状態が変わらない場合、
render
機能は変わりません。 明らかに、コンポーネントが同じままであれば、再度レンダリングする必要はありません。 何も変化しない場合、
render
関数は以前と同じものを返すため、実行する必要はありません。 このメカニズムにより、Reactは高速になります。 必要な場合にのみ何かが表示されます。
Reactは、標準のJavaScript機能を使用してコンポーネントのプロパティと状態が等しいかどうかをチェックします。つまり、
==
演算子を使用してそれらを単純に比較します。 Reactは、オブジェクトの等価性を判断するために、オブジェクトの「浅い」または「深い」比較を実行しません。 浅い比較は、メモリ内のオブジェクトのアドレスのみを比較する比較(オブジェクトへの参照)とは対照的に、オブジェクトの各キーと値のペアの比較を記述するために使用される概念です。 オブジェクトの「詳細な」比較はさらに進んでおり、オブジェクトの比較されたプロパティの値もオブジェクトである場合、これらのオブジェクトのキーと値のペアも比較されます。 このプロセスは、他のオブジェクトにネストされているすべてのオブジェクトに対して繰り返されます。 Reactはこの種のことは何もせず、リンクの等価性をチェックするだけです。
たとえば、フォーム
{ x: 1 }
オブジェクトで表されるコンポーネントのプロパティをまったく同じように見える別のオブジェクトに変更すると、これらのオブジェクトは異なるメモリ領域にあるため、Reactはコンポーネントを再レンダリングします。 上記の例を思い出すと、コンポーネントのプロパティを
object3
から
object1
に変更するとき、定数
object1
と
object3
は同じオブジェクトを参照するため、Reactはそのようなコンポーネントを再レンダリングしません。
JavaScriptでの関数の操作は、まったく同じ方法で構成されています。 Reactが異なるアドレスで同じ機能に遭遇した場合、再レンダリングされます。 「新しい関数」が、すでに使用されている関数への単なるリンクである場合、再レンダリングは行われません。
コンポーネントを扱う際の典型的な問題
コンポーネントを操作するシナリオの1つを次に示します。残念ながら、他の人のコードをチェックするときは常に私に出くわします。
class SomeComponent extends React.PureComponent { get instructions() { if (this.props.do) { return 'Click the button: '; } return 'Do NOT click the button: '; } render() { return ( <div> {this.instructions} <Button onClick={() => alert('!')} /> </div> ); } }
前にあるのは非常に単純なコンポーネントです。 これはボタンであり、クリックすると通知が表示されます。 ボタンの横には、使用方法が表示され、このボタンを押すかどうかをユーザーに通知します。 それらは
SomeComponent
コンポーネントの
do
(
do={true}
または
do={false}
)
SomeComponent
設定することで、表示される表示を正確に制御します。
SomeComponent
コンポーネントが再レンダリングされる
SomeComponent
(
do
プロパティの値が
true
から
false
、またはその逆に変更される場合)、
Button
エレメントもレンダリングされます。
onClick
ハンドラーは常に同じですが、
render
関数が呼び出されるたびに再作成されます。 その結果、コンポーネントがメモリに表示されるたびに、新しい関数が作成されます。その作成は
render
関数で実行される
render
、メモリ内の新しいアドレスへのリンクが
<Button />
渡され、
Button
コンポーネントも再レンダリングされますが、何も変わっていません。
修正方法について話します。
問題解決
関数がコンポーネント(
this
コンテキスト)から独立している場合、コンポーネントの外部で定義できます。 すべての場合で同じ関数になるため、コンポーネントのすべてのインスタンスは同じ関数参照を使用します。 これは次のようなものです。
const createAlertBox = () => alert('!'); class SomeComponent extends React.PureComponent { get instructions() { if (this.props.do) { return 'Click the button: '; } return 'Do NOT click the button: '; } render() { return ( <div> {this.instructions} <Button onClick={createAlertBox} /> </div> ); } }
前の例とは異なり、
createAlertBox
には
render
呼び出すたびに、メモリ内の同じ領域への同じリンクが含まれます。 その結果、
Button
繰り返し出力は実行されません。
Button
コンポーネントは小さく、すばやくレンダリングされますが、関数の内部宣言に関連する上記の問題は、レンダリングに多くの時間を要する大きくて複雑なコンポーネントにも見られます。 これにより、Reactアプリケーションの速度が大幅に低下する可能性があります。 この点で、そのような関数を
render
メソッド内で宣言するべきではないという推奨事項に従うことは理にかなってい
render
。
関数がコンポーネントに依存している場合、つまり、関数を外部で定義できない場合、コンポーネントメソッドをイベントハンドラとして渡すことができます。
class SomeComponent extends React.PureComponent { createAlertBox = () => { alert(this.props.message); }; get instructions() { if (this.props.do) { return 'Click the button: '; } return 'Do NOT click the button: '; } render() { return ( <div> {this.instructions} <Button onClick={this.createAlertBox} /> </div> ); } }
この場合、
SomeComponent
各インスタンスで、ボタンをクリックすると、さまざまなメッセージが表示されます。
Button
要素のイベントハンドラーは
SomeComponent
に一意である必要があります。
cteateAlertBox
メソッドを渡すとき、
SomeComponent
再レンダリングされるかどうかは関係ありません。
message
プロパティが変更されたかどうかは関係ありません。
createAlertBox
関数のアドレスは変更されません。つまり、
Button
要素を再度レンダリングすることはできません。 これにより、システムリソースを節約し、アプリケーションのレンダリング速度を向上させることができます。
これはすべて良いことです。 しかし、関数が動的な場合はどうでしょうか?
より複雑な問題を解決する
この資料の著者は、関数の再利用を説明するのに適した、最初に頭に浮かんだものを取り上げて、このセクションの例を準備したことに注意してください。 これらの例は、読者がアイデアの本質を理解するのに役立つことを目的としています。 何が起こっているのかを理解するためにこのセクションを読むことをお勧めしますが、Reactに組み込まれているキャッシュ無効化とメモリ管理メカニズムの機能を考慮した、ここで説明したメカニズムのより良いバージョンを提案した読者がいるため、著者は元の記事のコメントに注意することをお勧めします。
そのため、1つのコンポーネントには多くのユニークな動的イベントハンドラーが存在することは非常に一般的です。たとえば、同様のことがコードで見られ、
render
メソッドで
map
配列メソッドが使用され
render
。
class SomeComponent extends React.PureComponent { render() { return ( <ul> {this.props.list.map(listItem => <li key={listItem.text}> <Button onClick={() => alert(listItem.text)} /> </li> )} </ul> ); } }
ここでは、異なる数のボタンが表示され、異なる数のイベントハンドラーが作成されます。各イベントハンドラーは一意の関数で表されます。事前に
SomeComponent
作成する
SomeComponent
、これらの関数が何であるかは
SomeComponent
ません。 このパズルを解決するには?
ここでメモ化は、私たち、またはより簡単にキャッシュに役立ちます。 一意の値ごとに、関数を作成してキャッシュに入れます。 この一意の値が再び発生する場合、以前にキャッシュに配置されたそれに対応する関数をキャッシュから取得するだけで十分です。
このアイデアの実装は次のようになります。
class SomeComponent extends React.PureComponent { // SomeComponent // . clickHandlers = {}; // // . getClickHandler(key) { // , . if (!Object.prototype.hasOwnProperty.call(this.clickHandlers, key)) { this.clickHandlers[key] = () => alert(key); } return this.clickHandlers[key]; } render() { return ( <ul> {this.props.list.map(listItem => <li key={listItem.text}> <Button onClick={this.getClickHandler(listItem.text)} /> </li> )} </ul> ); } }
配列の各要素は、
getClickHandler
メソッドによって処理されます。 このメソッドは、特定の値で初めて呼び出されたときに、この値に固有の関数を作成し、キャッシュに入れて返します。 同じ値を渡してこのメソッドを呼び出すと、キャッシュから関数へのリンクが単純に返されます。
その結果、
SomeComponent
を再レンダリングしても
Button
は再レンダリングされません。 同様に、
list
プロパティに要素を追加すると、各ボタンのイベントハンドラーが動的に作成されます。
ハンドラーが複数の変数で定義されている場合、ハンドラーの一意の識別子を作成するには創造的である必要がありますが、これは
map
メソッドの結果として取得された各JSXオブジェクトの一意の
key
プロパティの通常の作成よりもそれほど複雑ではありません。
ここで、配列インデックスを識別子として使用する際に起こりうる問題について警告したいと思います。 実際、このアプローチでは、配列内の要素の順序が変更されたり、要素の一部が削除されたりすると、エラーが発生する可能性があります。 したがって、たとえば、最初にそのような配列が
[ 'soda', 'pizza' ]
ように見えてから
[ 'pizza' ]
に変わり、フォーム
listeners[0] = () => alert('soda')
コマンドを使用してイベントハンドラーをキャッシュした場合
listeners[0] = () => alert('soda')
、ユーザーが識別子0のハンドラーが割り当てられ、
[ 'pizza' ]
配列の内容に従って
pizza
メッセージを表示するボタンをクリックすると、
soda
メッセージが表示されることがわかります。 同じ理由で、キープロパティとして配列インデックスを使用することは推奨されません。
まとめ
この記事では、Reactアプリケーションのレンダリングを高速化できることを考慮して、内部JavaScriptメカニズムの機能を調べました。 ここで紹介したアイデアが役に立つことを願っています。
親愛なる読者! Reactアプリケーションを最適化するための興味深い方法を知っているなら、それらを共有してください。