Caching von Ereignishandlern und Verbesserung der Anwendungsleistung

Heute veröffentlichen wir eine Übersetzung des Materials, dessen Autor, nachdem er die Funktionen der Arbeit mit Objekten in JavaScript analysiert hat, React-Entwicklern eine Methode zur Beschleunigung von Anwendungen bietet. Insbesondere sprechen wir über die Tatsache, dass eine Variable, die, wie sie sagen, "einem Objekt zugewiesen" ist und oft einfach "Objekt" genannt wird, tatsächlich nicht das Objekt selbst speichert, sondern eine Verknüpfung dazu. Funktionen in JavaScript sind ebenfalls Objekte, daher gilt das oben Gesagte für sie. Wenn Sie dies berücksichtigen, können Sie durch das Entwerfen von React-Komponenten und die kritische Analyse ihres Codes ihre internen Mechanismen verbessern und die Anwendungsleistung verbessern.



Funktionen zum Arbeiten mit Objekten in JavaScript


Wenn Sie einige Funktionen erstellen, die genau gleich aussehen, und versuchen, sie zu vergleichen, stellt sich heraus, dass sie aus Sicht des Systems unterschiedlich sind. Um dies zu überprüfen, können Sie den folgenden Code ausführen:

const functionOne = function() { alert('Hello world!'); }; const functionTwo = function() { alert('Hello world!'); }; functionOne === functionTwo; // false 

Versuchen wir nun, einer vorhandenen Funktion, die bereits einer anderen Variablen zugewiesen ist, eine Variable zuzuweisen, und vergleichen Sie diese beiden Variablen:

 const functionThree = function() { alert('Hello world!'); }; const functionFour = functionThree; functionThree === functionFour; // true 

Wie Sie sehen können, gibt der strikte Gleichheitsoperator bei diesem Ansatz true .
Objekte verhalten sich natürlich genauso:

 const object1 = {}; const object2 = {}; const object3 = object1; object1 === object2; // false object1 === object3; // true 

Hier geht es um JavaScript. Wenn Sie jedoch Erfahrung in der Entwicklung in anderen Sprachen haben, sind Sie möglicherweise mit dem Konzept der Zeiger vertraut. Im obigen Code wird jedes Mal, wenn ein Objekt erstellt wird, ein Teil des Systemspeichers dafür zugewiesen. Wenn wir einen Befehl der Form object1 = {} , führt dies dazu, dass ein speziell für object1 zugewiesener Speicher mit einigen Daten gefüllt wird.

Man kann sich object1 durchaus als die Adresse vorstellen, an der sich die auf das Objekt bezogenen Datenstrukturen im Speicher befinden. Die Ausführung des Befehls object2 = {} führt zur Zuweisung eines anderen Speicherbereichs, der speziell für object2 . object2 obect1 und object2 im selben Speicherbereich? Nein, jeder von ihnen hat seine eigene Handlung. Deshalb werden wir false wenn wir versuchen, object2 und object2 zu vergleichen. Diese Objekte haben möglicherweise eine identische Struktur, aber die Adressen im Speicher, in dem sie sich befinden, unterscheiden sich, und es sind die Adressen, die während des Vergleichs überprüft werden.

Durch Ausführen des Befehls object3 = object1 schreiben wir die Adresse von object3 Konstante object3 . Dies ist kein neues Objekt. Dieser Konstante wird die Adresse eines vorhandenen Objekts zugewiesen. Sie können dies überprüfen, indem Sie:

 const object1 = { x: true }; const object3 = object1; object3.x = false; object1.x; // false 

In diesem Beispiel wird ein Objekt im Speicher erstellt und seine Adresse in die Konstante object1 . Dann wird dieselbe Adresse in das konstante object3 . Durch Ändern von object3 wird das Objekt im Speicher object3 . Dies bedeutet, dass wir beim Zugriff auf ein Objekt mit einem anderen Verweis darauf, beispielsweise dem in object1 gespeicherten, bereits mit seiner geänderten Version arbeiten.

Funktionen, Objekte und Reaktion


Ein Missverständnis des oben genannten Mechanismus durch unerfahrene Entwickler führt häufig zu Fehlern, und möglicherweise ist die Berücksichtigung der Merkmale der Arbeit mit Objekten eines separaten Artikels würdig. Unser heutiges Thema ist jedoch die Leistung von React-Anwendungen. In diesem Bereich können selbst von ziemlich erfahrenen Entwicklern Fehler gemacht werden, die einfach nicht darauf achten, wie React-Anwendungen von der Tatsache beeinflusst werden, dass JavaScript-Variablen und -Konstanten nicht in den Objekten selbst gespeichert werden, sondern nur auf diese verlinken.

Was hat das mit React zu tun? React verfügt über intelligente Mechanismen zum Einsparen von Systemressourcen zur Verbesserung der Anwendungsleistung: Wenn sich die Eigenschaften und der Status der Komponente nicht ändern, ändert sich auch nichts an der Renderfunktion. Wenn die Komponente dieselbe bleibt, muss sie natürlich nicht erneut gerendert werden. Wenn sich nichts ändert, gibt die render dieselbe wie zuvor zurück, sodass sie nicht ausgeführt werden muss. Dieser Mechanismus macht React schnell. Etwas wird nur bei Bedarf angezeigt.

React überprüft die Eigenschaften und den Status von Komponenten mithilfe von Standard-JavaScript-Funktionen auf Gleichheit. Das heißt, es werden sie einfach mit dem Operator == verglichen. React führt keinen "flachen" oder "tiefen" Vergleich von Objekten durch, um deren Gleichheit zu bestimmen. Ein "flacher Vergleich" ist ein Konzept, das verwendet wird, um einen Vergleich jedes Schlüssel-Wert-Paares eines Objekts zu beschreiben, im Gegensatz zu einem Vergleich, bei dem nur die Adressen von Objekten im Speicher verglichen werden (Verweise auf diese). Der „tiefe“ Vergleich von Objekten geht noch weiter, und wenn die Werte der verglichenen Eigenschaften der Objekte auch Objekte sind, vergleichen sie auch die Schlüssel-Wert-Paare dieser Objekte. Dieser Vorgang wird für alle in anderen Objekten verschachtelten Objekte wiederholt. React macht nichts dergleichen, sondern prüft nur die Gleichheit der Links.

Wenn Sie beispielsweise die Eigenschaft einer Komponente, die durch ein Objekt der Form { x: 1 } in ein anderes Objekt ändern, das genau gleich aussieht, wird die Komponente von React erneut gerendert, da sich diese Objekte in verschiedenen Speicherbereichen befinden. Wenn Sie sich an das obige Beispiel erinnern, wird beim Ändern der Eigenschaften einer Komponente von object1 in object3 eine solche Komponente von React nicht erneut object3 , da sich die Konstanten object1 und object3 auf dasselbe Objekt beziehen.

Das Arbeiten mit Funktionen in JavaScript ist genauso organisiert. Wenn React auf dieselben Funktionen stößt, deren Adressen unterschiedlich sind, wird es erneut gerendert. Wenn die „neue Funktion“ nur eine Verknüpfung zu einer bereits verwendeten Funktion ist, erfolgt kein erneutes Rendern.

Ein typisches Problem bei der Arbeit mit Komponenten


Hier ist eines der Szenarien für die Arbeit mit Komponenten, die mir leider ständig beim Überprüfen des Codes eines anderen auffallen:

 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>   ); } } 

Vor uns liegt eine sehr einfache Komponente. Es ist eine Schaltfläche. Wenn Sie darauf klicken, wird eine Benachrichtigung angezeigt. Neben der Schaltfläche werden Anweisungen zur Verwendung angezeigt, die den Benutzer darüber informieren, ob er diese Schaltfläche drücken soll. Steuern Sie, welche Anweisung angezeigt wird, indem Sie die SomeComponent do ( do={true} oder do={false} ) SomeComponent .

Jedes Mal, SomeComponent die SomeComponent Komponente erneut gerendert wird (wenn der Wert der Eigenschaft do von true in false geändert wird und umgekehrt), wird auch das Button Element gerendert. Der onClick Handler wird bei jedem onClick der onClick neu erstellt, obwohl er immer derselbe ist. Infolgedessen stellt sich heraus, dass jedes Mal, wenn die Komponente im Speicher angezeigt wird, eine neue Funktion erstellt wird, da ihre Erstellung in der render wird, eine Verknüpfung mit der neuen Adresse im Speicher an <Button /> wird und die Button Komponente trotz der Tatsache, dass in erneut gerendert wird nichts hat sich geändert.

Lassen Sie uns darüber sprechen, wie Sie das Problem beheben können.

Lösung


Wenn die Funktion unabhängig von der Komponente ist ( this Kontext), können Sie sie außerhalb der Komponente definieren. Alle Instanzen der Komponente verwenden dieselbe Funktionsreferenz, da es sich in allen Fällen um dieselbe Funktion handelt. So sieht es aus:

 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>   ); } } 

Im Gegensatz zum vorherigen Beispiel enthält createAlertBox bei jedem Aufruf zum render denselben Link zu demselben Bereich im Speicher. Infolgedessen wird Button wiederholte Ausgabe von Button nicht ausgeführt.

Während die Button Komponente klein ist und schnell gerendert wird, kann das obige Problem, das mit der internen Deklaration von Funktionen verbunden ist, auch in großen, komplexen Komponenten auftreten, deren Rendern viel Zeit in Anspruch nimmt. Dies kann die React-Anwendung erheblich verlangsamen. In diesem Zusammenhang ist es sinnvoll, der Empfehlung zu folgen, wonach solche Funktionen niemals innerhalb der render deklariert werden sollten.

Wenn die Funktion von der Komponente abhängt, dh nicht außerhalb der Komponente definiert werden kann, kann die Komponentenmethode als Ereignishandler übergeben werden:

 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>   ); } } 

In diesem Fall werden in jeder Instanz von SomeComponent beim Klicken auf die Schaltfläche verschiedene Meldungen angezeigt. Der Ereignishandler des Button Elements muss für SomeComponent eindeutig SomeComponent . Beim Übergeben der cteateAlertBox Methode spielt cteateAlertBox keine Rolle, ob SomeComponent neu gerendert wird. Es spielt keine Rolle, ob sich die message geändert hat. Die Adresse der Funktion createAlertBox nicht. createAlertBox bedeutet, dass das Button Element nicht erneut gerendert werden sollte. Dank dieser Funktion können Sie Systemressourcen sparen und die Rendergeschwindigkeit der Anwendung verbessern.

Das alles ist gut. Was aber, wenn Funktionen dynamisch sind?

Ein komplexeres Problem lösen


Der Autor dieses Materials bittet Sie, darauf zu achten, dass er die Beispiele in diesem Abschnitt vorbereitet hat, wobei er sich als erstes Gedanken über die Wiederverwendung von Funktionen gemacht hat. Diese Beispiele sollen dem Leser helfen, das Wesentliche der Idee zu erfassen. Obwohl dieser Abschnitt zum Lesen empfohlen wird, um die Essenz des Geschehens zu verstehen, empfiehlt der Autor, auf Kommentare zum Originalartikel zu achten, da einige Leser bessere Versionen der hier diskutierten Mechanismen vorgeschlagen haben, die Merkmale der in React integrierten Cache-Invalidierung und Speicherverwaltungsmechanismen berücksichtigen.

Daher ist es äußerst häufig, dass in einer Komponente viele eindeutige, dynamische Ereignishandler vorhanden sind. Beispielsweise ist im Code etwas Ähnliches zu sehen, in dem die map Array-Methode in der render Methode verwendet wird:

 class SomeComponent extends React.PureComponent { render() {   return (     <ul>       {this.props.list.map(listItem =>         <li key={listItem.text}>           <Button onClick={() => alert(listItem.text)} />         </li>       )}     </ul>   ); } } 

Hier wird eine andere Anzahl von Schaltflächen angezeigt und eine andere Anzahl von Ereignishandlern erstellt, von denen jede durch eine eindeutige Funktion dargestellt wird. Im Voraus ist beim Erstellen von SomeComponent nicht bekannt, um welche Funktionen es sich handelt. Wie löse ich dieses Rätsel?

Hier hilft uns das Auswendiglernen oder einfacher das Zwischenspeichern. Erstellen Sie für jeden eindeutigen Wert eine Funktion und legen Sie sie im Cache ab. Wenn dieser eindeutige Wert erneut auftritt, reicht es aus, die entsprechende Funktion aus dem Cache zu entnehmen, die zuvor im Cache abgelegt wurde.

So sieht die Umsetzung dieser Idee aus:

 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>   ); } } 

Jedes Element des Arrays wird von der Methode getClickHandler verarbeitet. Diese Methode erstellt beim ersten Aufruf mit einem bestimmten Wert eine für diesen Wert eindeutige Funktion, legt sie im Cache ab und gibt sie zurück. Bei allen nachfolgenden Aufrufen dieser Methode, bei denen derselbe Wert übergeben wird, wird einfach eine Verknüpfung zur Funktion aus dem Cache zurückgegeben.

Infolgedessen wird beim erneuten Rendern von SomeComponent die SomeComponent nicht erneut SomeComponent . In ähnlicher Weise werden durch Hinzufügen von Elementen zur list dynamisch Ereignishandler für jede Schaltfläche erstellt.

Sie müssen kreativ sein, um eindeutige Bezeichner für Handler zu erstellen, wenn diese durch mehr als eine Variable definiert sind. Dies ist jedoch nicht viel komplizierter als die übliche Erstellung einer eindeutigen key für jedes JSX-Objekt, das als Ergebnis der map wird.

Hier möchte ich Sie vor möglichen Problemen bei der Verwendung von Array-Indizes als Bezeichner warnen. Tatsache ist, dass bei diesem Ansatz Fehler auftreten können, wenn sich die Reihenfolge der Elemente im Array ändert oder einige seiner Elemente gelöscht werden. Wenn beispielsweise ein ähnliches Array zunächst wie [ 'soda', 'pizza' ] aussah und dann in [ 'pizza' ] wurde und Sie Ereignishandler mit einem Befehl der Formular- listeners[0] = () => alert('soda') , wenn der Benutzer auf die Schaltfläche klickt, der der Handler mit der Kennung 0 zugewiesen ist und die gemäß dem Inhalt des Arrays [ 'pizza' ] eine pizza Nachricht anzeigen soll, wird eine soda Nachricht angezeigt. Aus dem gleichen Grund wird nicht empfohlen, Array-Indizes als Schlüsseleigenschaften zu verwenden.

Zusammenfassung


In diesem Artikel haben wir die Funktionen der internen JavaScript-Mechanismen untersucht und berücksichtigt, mit denen Sie das Rendern von React-Anwendungen beschleunigen können. Wir hoffen, dass die hier vorgestellten Ideen nützlich sind.

Liebe Leser! Wenn Sie interessante Möglichkeiten zur Optimierung von React-Anwendungen kennen, teilen Sie diese bitte mit.

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


All Articles