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;
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;
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;
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 {
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.
