Effiziente Reaktionskomponenten: Ein Leitfaden zur Optimierung der Reaktionsleistung
Veröffentlicht: 2022-03-11Seit seiner Einführung hat React die Art und Weise verändert, wie Frontend-Entwickler über die Entwicklung von Web-Apps denken. Mit virtuellem DOM macht React UI-Updates so effizient wie nie zuvor und macht Ihre Web-App schneller. Aber warum neigen mittelgroße React-Web-Apps immer noch dazu, schlecht zu funktionieren?
Nun, der Hinweis liegt darin, wie Sie React verwenden.
Eine moderne Front-End-Bibliothek wie React macht Ihre App nicht auf magische Weise schneller. Der Entwickler muss verstehen, wie React funktioniert und wie die Komponenten die verschiedenen Phasen des Komponentenlebenszyklus durchlaufen.
Mit React können Sie viele der Leistungsverbesserungen erzielen, die es zu bieten hat, indem Sie messen und optimieren, wie und wann Ihre Komponenten rendern. Und React bietet genau die Tools und Funktionen, die erforderlich sind, um dies zu vereinfachen.
In diesem React-Tutorial erfahren Sie, wie Sie die Leistung Ihrer React-Komponenten messen und optimieren können, um eine viel leistungsfähigere React-Webanwendung zu erstellen. Sie erfahren auch, wie einige Best Practices für JavaScript dazu beitragen, dass Ihre React-Webanwendung eine viel flüssigere Benutzererfahrung bietet.
Wie funktioniert Reagieren?
Bevor wir in die Optimierungstechniken eintauchen, müssen wir besser verstehen, wie React funktioniert.
Im Mittelpunkt der React-Entwicklung stehen die einfache und offensichtliche JSX-Syntax und die Fähigkeit von React, virtuelle DOMs zu erstellen und zu vergleichen. Seit seiner Veröffentlichung hat React viele andere Frontend-Bibliotheken beeinflusst. Auch Bibliotheken wie Vue.js setzen auf die Idee virtueller DOMs.
So funktioniert React:
Jede React-Anwendung beginnt mit einer Root-Komponente und besteht aus vielen Komponenten in einer Baumformation. Komponenten in React sind „Funktionen“, die die Benutzeroberfläche basierend auf den empfangenen Daten (Requisiten und Status) rendern.
Wir können dies als F
symbolisieren.
UI = F(data)
Benutzer interagieren mit der Benutzeroberfläche und bewirken, dass sich die Daten ändern. Unabhängig davon, ob die Interaktion das Klicken auf eine Schaltfläche, das Tippen auf ein Bild, das Herumziehen von Listenelementen, AJAX-Anforderungen zum Aufrufen von APIs usw. beinhaltet, all diese Interaktionen ändern nur die Daten. Sie bewirken niemals, dass sich die Benutzeroberfläche direkt ändert.
Daten sind hier alles, was den Zustand der Webanwendung definiert, und nicht nur das, was Sie in Ihrer Datenbank gespeichert haben. Sogar Bits von Front-End-Zuständen (z. B. welche Registerkarte gerade ausgewählt ist oder ob ein Kontrollkästchen gerade aktiviert ist) sind Teil dieser Daten.
Immer wenn sich diese Daten ändern, verwendet React die Komponentenfunktionen, um die Benutzeroberfläche neu zu rendern, aber nur virtuell:
UI1 = F(data1) UI2 = F(data2)
React berechnet die Unterschiede zwischen der aktuellen Benutzeroberfläche und der neuen Benutzeroberfläche, indem es einen Vergleichsalgorithmus auf die beiden Versionen seines virtuellen DOM anwendet.
Changes = Diff(UI1, UI2)
React fährt dann damit fort, nur die UI-Änderungen auf die echte UI im Browser anzuwenden.
Wenn sich die mit einer Komponente verbundenen Daten ändern, bestimmt React, ob eine tatsächliche DOM-Aktualisierung erforderlich ist. Auf diese Weise kann React potenziell teure DOM-Manipulationsvorgänge im Browser vermeiden, wie z. B. das Erstellen von DOM-Knoten und den Zugriff auf vorhandene über die Notwendigkeit hinaus.
Dieses wiederholte Abgleichen und Rendern von Komponenten kann eine der Hauptursachen für React-Leistungsprobleme in jeder React-App sein. Das Erstellen einer React-App, bei der der Vergleichsalgorithmus nicht effektiv abgestimmt werden kann, was dazu führt, dass die gesamte App wiederholt gerendert wird, kann zu einer frustrierend langsamen Erfahrung führen.
Wo fange ich mit der Optimierung an?
Aber was genau optimieren wir?
Sie sehen, während des anfänglichen Renderprozesses baut React einen DOM-Baum wie diesen auf:
Angesichts eines Teils der Datenänderungen möchten wir, dass React nur die Komponenten neu rendert, die direkt von der Änderung betroffen sind (und möglicherweise sogar den Diff-Prozess für die restlichen Komponenten überspringen):
Was React jedoch letztendlich tut, ist:
In der obigen Abbildung werden alle gelben Knoten gerendert und diffundiert, was zu verschwendeter Zeit/Rechenressourcen führt. Hier werden wir unsere Optimierungsbemühungen in erster Linie einsetzen. Wenn Sie jede Komponente so konfigurieren, dass sie nur dann gerendert wird, wenn es notwendig ist, können wir diese verschwendeten CPU-Zyklen zurückgewinnen.
Die Entwickler der React-Bibliothek haben dies berücksichtigt und uns einen Haken bereitgestellt, um genau das zu tun: eine Funktion, mit der wir React mitteilen können, wann es in Ordnung ist, das Rendern einer Komponente zu überspringen.
Zuerst messen
Wie Rob Pike es ziemlich elegant als eine seiner Programmierregeln formuliert:
Messen. Stellen Sie die Geschwindigkeit erst ein, wenn Sie gemessen haben, und selbst dann nicht, es sei denn, ein Teil des Codes überwältigt den Rest.
Beginnen Sie nicht mit der Optimierung von Code, von dem Sie glauben, dass er Ihre App verlangsamt. Lassen Sie sich stattdessen von den Tools zur Leistungsmessung von React durch den Weg führen.
React hat genau dafür ein leistungsstarkes Tool. Mit der react-addons-perf
Bibliothek können Sie sich einen Überblick über die Gesamtleistung Ihrer App verschaffen.
Die Verwendung ist sehr einfach:
Import Perf from 'react-addons-perf' Perf.start(); // use the app Perf.stop(); Perf.printWasted();
Dadurch wird eine Tabelle mit der Menge an Zeitkomponenten gedruckt, die beim Rendern verschwendet wurden.
Die Bibliothek bietet andere Funktionen, mit denen Sie verschiedene Aspekte der verschwendeten Zeit separat drucken können (z. B. mit den printInclusive()
oder printExclusive()
) oder sogar die DOM-Manipulationsoperationen drucken (mit der Funktion printOperations()
).
Benchmarking einen Schritt weiter
Wenn Sie ein visueller Mensch sind, dann ist das react-perf-tool
genau das Richtige für Sie.
react-perf-tool
basiert auf der react-addons-perf
Bibliothek. Es gibt Ihnen eine visuellere Möglichkeit, die Leistung Ihrer React-App zu debuggen. Es verwendet die zugrunde liegende Bibliothek, um Messungen zu erhalten, und visualisiert sie dann als Diagramme.
In den meisten Fällen ist dies eine viel bequemere Methode, um Engpässe zu erkennen. Sie können es einfach verwenden, indem Sie es als Komponente zu Ihrer Anwendung hinzufügen.
Sollte React die Komponente aktualisieren?
Standardmäßig wird React ausgeführt, das virtuelle DOM gerendert und die Unterschiede für jede Komponente im Baum auf Änderungen in ihren Eigenschaften oder ihrem Zustand verglichen. Aber das ist offensichtlich nicht vernünftig.
Wenn Ihre App wächst, wird der Versuch, das gesamte virtuelle DOM bei jeder Aktion neu zu rendern und zu vergleichen, schließlich langsamer.
React bietet dem Entwickler eine einfache Möglichkeit anzugeben, ob eine Komponente neu gerendert werden muss. Hier kommt die shouldComponentUpdate
Methode ins Spiel.
function shouldComponentUpdate(nextProps, nextState) { return true; }
Wenn diese Funktion für eine beliebige Komponente wahr zurückgibt, ermöglicht sie das Auslösen des Render-Diff-Prozesses.
Dies gibt Ihnen eine einfache Möglichkeit, den Render-Diff-Prozess zu steuern. Wann immer Sie verhindern müssen, dass eine Komponente überhaupt neu gerendert wird, geben Sie einfach false
von der Funktion zurück. Innerhalb der Funktion können Sie den aktuellen und den nächsten Satz von Requisiten und Status vergleichen, um festzustellen, ob ein erneutes Rendern erforderlich ist:

function shouldComponentUpdate(nextProps, nextState) { return nextProps.id !== this.props.id; }
Verwenden einer React.PureComponent
Um diese Optimierungstechnik ein wenig zu vereinfachen und zu automatisieren, bietet React eine sogenannte „reine“ Komponente. Eine React.PureComponent
ist genau wie eine React.Component
, die eine shouldComponentUpdate()
Funktion mit einem flachen Prop- und Statusvergleich implementiert.
Eine React.PureComponent
ist mehr oder weniger äquivalent dazu:
class MyComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { return shallowCompare(this.props, nextProps) && shallowCompare(this.state, nextState); } … }
Da es nur einen oberflächlichen Vergleich durchführt, ist es möglicherweise nur dann nützlich, wenn:
- Ihre Requisiten oder Zustände enthalten primitive Daten.
- Ihre Requisiten und Zustände haben komplexe Daten, aber Sie wissen, wann Sie
forceUpdate()
aufrufen müssen, um Ihre Komponente zu aktualisieren.
Daten unveränderlich machen
Was wäre, wenn Sie eine React.PureComponent
verwenden könnten, aber dennoch eine effiziente Möglichkeit hätten, festzustellen, wann sich komplexe Requisiten oder Zustände automatisch geändert haben? Hier erleichtern unveränderliche Datenstrukturen das Leben.
Die Idee hinter der Verwendung unveränderlicher Datenstrukturen ist einfach. Wenn sich ein Objekt mit komplexen Daten ändert, erstellen Sie eine Kopie dieses Objekts mit den Änderungen, anstatt die Änderungen in diesem Objekt vorzunehmen. Dadurch wird das Erkennen von Datenänderungen so einfach wie das Vergleichen der Referenz der beiden Objekte.
Sie können Object.assign
oder _.extend
(von Underscore.js oder Lodash) verwenden:
const newValue2 = Object.assign({}, oldValue); const newValue2 = _.extend({}, oldValue);
Noch besser, Sie können eine Bibliothek verwenden, die unveränderliche Datenstrukturen bereitstellt:
var map1 = Immutable.Map({a:1, b:2, c:3}); var map2 = map1.set('b', 2); assert(map1.equals(map2) === true); var map3 = map1.set('b', 50); assert(map1.equals(map3) === false);
Hier wird Immutable.Map
von der Bibliothek Immutable.js bereitgestellt.
Jedes Mal, wenn eine Map mit ihrer Methode set
aktualisiert wird, wird nur dann eine neue Map zurückgegeben, wenn die Set-Operation den zugrunde liegenden Wert geändert hat. Andernfalls wird dieselbe Karte zurückgegeben.
Hier erfahren Sie mehr über die Verwendung unveränderlicher Datenstrukturen.
Weitere React-App-Optimierungstechniken
Verwenden des Produktions-Builds
Bei der Entwicklung einer React-App werden Ihnen wirklich nützliche Warnungen und Fehlermeldungen präsentiert. Diese machen das Identifizieren von Fehlern und Problemen während der Entwicklung zu einem Glücksfall. Aber sie kosten Leistung.
Wenn Sie sich den Quellcode von React ansehen, werden Sie viele if (process.env.NODE_ENV != 'production')
Prüfungen sehen. Diese Codeabschnitte, die React in Ihrer Entwicklungsumgebung ausführt, werden vom Endbenutzer nicht benötigt. Für Produktionsumgebungen kann dieser gesamte unnötige Code verworfen werden.
Wenn Sie Ihr Projekt mit create-react-app
, können Sie einfach npm run build
um den Produktions-Build ohne diesen zusätzlichen Code zu erstellen. Wenn Sie Webpack direkt verwenden, können Sie webpack -p
(entspricht webpack --optimize-minimize --define process.env.NODE_ENV="'production'"
.
Bindungsfunktionen früh
Es ist sehr üblich, Funktionen zu sehen, die an den Kontext der Komponente innerhalb der Renderfunktion gebunden sind. Dies ist häufig der Fall, wenn wir diese Funktionen verwenden, um Ereignisse von untergeordneten Komponenten zu behandeln.
// Creates a new `handleUpload` function during each render() <TopBar onUpload={this.handleUpload.bind(this)} /> // ...as do inlined arrow functions <TopBar onUpload={files => this.handleUpload(files)} />
Dadurch erstellt die Funktion render()
bei jedem Rendern eine neue Funktion. Ein viel besserer Weg, dasselbe zu tun, ist:
class App extends React.Component { constructor(props) { super(props); this.handleUpload = this.handleUpload.bind(this); } render() { … <TopBar onUpload={this.handleUpload} /> … } }
Verwenden mehrerer Chunk-Dateien
Bei einseitigen React-Web-Apps bündeln wir häufig unseren gesamten Front-End-JavaScript-Code in einer einzigen minimierten Datei. Dies funktioniert gut für kleine bis mittelgroße Web-Apps. Aber wenn die App zu wachsen beginnt, kann die Bereitstellung dieser gebündelten JavaScript-Datei an den Browser selbst zu einem zeitaufwändigen Prozess werden.
Wenn Sie Webpack zum Erstellen Ihrer React-App verwenden, können Sie dessen Code-Splitting-Funktionen nutzen, um Ihren erstellten App-Code in mehrere „Blöcke“ aufzuteilen und diese nach Bedarf an den Browser zu liefern.
Es gibt zwei Arten von Splitting: Ressourcen-Splitting und On-Demand-Code-Splitting.
Mit der Ressourcenaufteilung teilen Sie Ressourceninhalte in mehrere Dateien auf. Mit CommonsChunkPlugin können Sie beispielsweise allgemeinen Code (wie alle externen Bibliotheken) in eine eigene „Chunk“-Datei extrahieren. Mit ExtractTextWebpackPlugin können Sie den gesamten CSS-Code in eine separate CSS-Datei extrahieren.
Diese Art der Aufteilung hilft auf zweierlei Weise. Es hilft dem Browser, diese weniger häufig wechselnden Ressourcen zwischenzuspeichern. Es hilft dem Browser auch, die Vorteile des parallelen Downloads zu nutzen, um die Ladezeit möglicherweise zu verkürzen.
Eine bemerkenswertere Funktion von Webpack ist das On-Demand-Code-Splitting. Sie können es verwenden, um Code in einen Chunk aufzuteilen, der bei Bedarf geladen werden kann. Dadurch kann der anfängliche Download klein gehalten werden, was die Ladezeit der App verkürzt. Der Browser kann dann andere Codeblöcke bei Bedarf herunterladen, wenn die Anwendung dies benötigt.
Hier erfahren Sie mehr über das Teilen von Webpack-Code.
Aktivieren von Gzip auf Ihrem Webserver
Die Bundle-JS-Dateien der React-App sind normalerweise sehr groß. Um das Laden der Webseite zu beschleunigen, können wir Gzip auf dem Webserver aktivieren (Apache, Nginx usw.).
Moderne Browser unterstützen alle die Gzip-Komprimierung und handeln diese automatisch für HTTP-Anfragen aus. Durch Aktivieren der Gzip-Komprimierung kann die Größe der übertragenen Antwort um bis zu 90 % reduziert werden, was die Zeit zum Herunterladen der Ressource erheblich verkürzen, die Datennutzung für den Client reduzieren und die Zeit bis zum ersten Rendern Ihrer Seiten verkürzen kann.
Sehen Sie in der Dokumentation Ihres Webservers nach, wie Sie die Komprimierung aktivieren:
- Apache: Verwenden Sie mod_deflate
- Nginx: Verwenden Sie ngx_http_gzip_module
Verwenden von Eslint-plugin-react
Sie sollten ESLint für fast jedes JavaScript-Projekt verwenden. Reagieren ist nicht anders.
Mit eslint-plugin-react
zwingen Sie sich dazu, sich an viele Regeln in der React-Programmierung anzupassen, die Ihrem Code langfristig zugute kommen und viele häufige Probleme und Probleme vermeiden, die aufgrund von schlecht geschriebenem Code auftreten.
Machen Sie Ihre React Web Apps wieder schnell
Um das Beste aus React herauszuholen, müssen Sie seine Tools und Techniken nutzen. Die Leistung einer React-Webanwendung liegt in der Einfachheit ihrer Komponenten. Das Überfordern des Render-Diff-Algorithmus kann dazu führen, dass Ihre App auf frustrierende Weise schlecht funktioniert.
Bevor Sie Ihre App optimieren können, müssen Sie verstehen, wie React-Komponenten funktionieren und wie sie im Browser gerendert werden. Die React-Lebenszyklusmethoden bieten Ihnen Möglichkeiten, um zu verhindern, dass Ihre Komponente unnötig neu gerendert wird. Beseitigen Sie diese Engpässe und Sie erhalten die App-Leistung, die Ihre Benutzer verdienen.
Obwohl es mehr Möglichkeiten gibt, eine React-Web-App zu optimieren, führt die Feinabstimmung der Komponenten so, dass sie nur bei Bedarf aktualisiert werden, zur besten Leistungsverbesserung.
Wie messen und optimieren Sie die Leistung Ihrer React-Webanwendung? Teilen Sie es in den Kommentaren unten.