Performance-Optimierung
Intern verwendet React mehrere clevere Techniken, um die Anzahl kostspieliger DOM-Vorgänge zu minimieren, die nötig sind, um die Benutzeroberfläche zu aktualisieren. Für viele Anwendungen führt React zu einer schnellen Benutzeroberfläche, ohne dass die Performance mit viel Aufwand optimiert werden muss. Trotzdem gibt es mehrere Möglichkeiten, wie du deine React-Anwendung noch schneller machen kannst.
Benutze den Produktions-Build
Falls du Benchmarking betreibst oder Performance-Probleme in deinen React-Apps feststellst, stelle sicher, dass du sie mit dem minifizierten Produktions-Build testest.
Standardmäßig enthält React viele hilfreiche Warnungen. Diese Warnungen sind während des Entwickelns sehr nützlich. Allerdings machen sie React größer und langsamer, weshalb du beim Deployen darauf achten solltest, die Produktions-Version zu verwenden.
Falls du nicht sicher bist, ob dein Build-Prozess korrekt eingerichtet ist, kannst du ihn überprüfen, indem du die React Developer Tools für Chrome installierst. Wenn du eine Seite mit React im Produktions-Modus besuchst, hat das Icon einen dunklen Hintergrund:
Wenn du eine Seite mit React im Entwickler-Modus besuchst, hat das Icon einen roten Hintergrund:
Der Entwickler-Modus sollte benutzt werden, während du an der App arbeitest, und der Produktions-Modus, wenn du die Webseite deployst.
Weiter unten findest du eine Anleitung, wie du die Produktions-Version deiner App erstellst.
Create React App
Falls dein Projekt mit Create React App erstellt wurde, führe Folgendes aus:
npm run build
Damit wird im build/
-Ordner deines Projekts ein Produktions-Build deiner App erstellt.
Denke daran, dass das nur erforderlich ist, bevor du deine App deployst. Zum normalen Entwickeln benutze npm start
.
Einzeldatei-Builds
Wir bieten produktionsfertige Versionen von React und React DOM als Einzeldateien an:
<script src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
Denke daran, dass nur React-Dateien, die mit .production.min.js
enden, für die Produktionsumgebung geeignet sind.
Brunch
Um den effizientesten Brunch-Produktions-Build zu bekommen, installiere das terser-brunch
-Plugin:
# Falls du npm verwendest
npm install --save-dev terser-brunch
# Falls du Yarn verwendest
yarn add --dev terser-brunch
Um dann einen Produktions-Build zu erstellen, füge zum build
-Befehl das -p
-Flag hinzu:
brunch build -p
Denke daran, dass du das nur für die Produktionsumgebung machen musst. Du solltest das -p
-Flag oder dieses Plugin nicht während der Entwicklung verwenden, da sie nützliche React-Warnungen verbergen und die Builds viel langsamer machen.
Browserify
Um den effizientesten Browserify-Produktions-Build zu bekommen, installiere ein paar Plugins:
# Falls du npm verwendest
npm install --save-dev envify terser uglifyify
# Falls du Yarn verwendest
yarn add --dev envify terser uglifyify
Um einen Produktions-Build zu erstellen, stelle sicher, dass du diese Transformer hinzufügst (die Reihenfolge ist wichtig):
- Der
envify
-Transformer stellt sicher, dass die richtige Build-Umgebung gesetzt wird. Benutze ihn global (-g
). - Der
uglifyify
-Transformer entfernt Entwickler-Importe. Benutze auch ihn global (-g
). - Schließlich wird das so entstandene Bundle an
terser
geleitet, wo es “gemangled” wird (lies warum).
Zum Beispiel:
browserify ./index.js \
-g [ envify --NODE_ENV production ] \
-g uglifyify \
| terser --compress --mangle > ./bundle.js
Denke daran, dass du das nur für die Produktionsumgebung machen musst. Du solltest diese Plugins nicht während der Entwicklung verwenden, da sie nützliche React-Warnungen verbergen und die Builds viel langsamer machen.
Rollup
Um den effizientesten Rollup-Produktions-Build zu bekommen, installiere ein paar Plugins:
# Falls du npm benutzt
npm install --save-dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-terser
# Falls du Yarn benutzt
yarn add --dev rollup-plugin-commonjs rollup-plugin-replace rollup-plugin-terser
Um einen Produktions-Build zu kreieren, stelle sicher, dass du diese Plugins hinzufügst (die Reihenfolge ist wichtig):
- Das
replace
-Plugin stellt sicher, dass die richtige Build-Umgebung gesetzt wird. - Das
commonjs
-Plugin bietet CommonJS-Unterstützung in Rollup. - Das [
terser
](https://github.com/TrySound/rollup-plugin-terser-Plugin komprimiert und “mangled” das finale Bundle.
plugins: [
// ...
require('rollup-plugin-replace')({
'process.env.NODE_ENV': JSON.stringify('production')
}),
require('rollup-plugin-commonjs')(),
require('rollup-plugin-terser')(),
// ...
]
Siehe dir ein komplettes Setup-Beispiel in diesem Gist an.
Denke daran, dass du das nur für die Produktionsumgebung machen musst. Du solltest das terser
-Plugin oder das replace
-Plugin mit 'production'
nicht während der Entwicklung verwenden, da sie nützliche React-Warnungen verbergen und die Builds viel langsamer machen.
webpack
Hinweis:
Falls du Create React App verwendest, folge bitte den Anweisungen weiter oben.
Dieser Abschnitt ist nur relevant, wenn du webpack direkt konfigurierst.
Webpack v4+ minified deinen Code standardmäßig im production
-Modus.
const TerserPlugin = require('terser-webpack-plugin');
module.exports = {
mode: 'production',
optimization: {
minimizer: [new TerserPlugin({ /* additional options here */ })],
},
};
Mehr Informationen darüber findest du in der webpack-Dokumentation.
Denke daran, dass du das nur für die Produktionsumgebung machen musst. Du solltest TerserPlugin
nicht während der Entwicklung verwenden, da es nützliche React-Warnungen verbergen wird und die Builds viel langsamer macht.
Komponenten-Profiling mit dem DevTools-Profiler
react-dom
16.5+ und react-native
0.57+ bieten erweiterte Profiling-Funktionen im DEV-Modus mit dem React DevTools-Profiler.
Eine Übersicht über den Profiler gibt es im Blog-Post “Vorstellung des React Profilers”.
Es gibt auch eine Video-Anleitung zum Profiler auf YouTube.
Falls du die React DevTools noch nicht installiert hast, kannst du sie hier finden:
Hinweis
Ein Produktions-Profiling-Bundle von
react-dom
ist auch alsreact-dom/profiling
verfügbar. Lese mehr über die Verwendung dieses Bundles auf fb.me/react-profiling.
Note
Before React 17, we use the standard User Timing API to profile components with the chrome performance tab. For a more detailed walkthrough, check out this article by Ben Schwarz.
Virtualisieren von langen Listen
Falls deine Anwendung lange Datenlisten (mit Hunderten oder Tausenden von Zeilen) rendert, empfehlen die Verwendung einer Technik, die als “Windowing” bekannt ist. Diese Technik rendert zu jedem Zeitpunkt nur eine kleine Teilmenge deiner Zeilen. Dadurch kannst du sowohl die Zeit, die benötigt wird, um die Komponenten erneut zu rendern, als auch die Anzahl der erstellten DOM-Knoten drastisch reduzieren.
react-window und react-virtualized sind beliebte Windowing-Bibliotheken. Sie bieten eine Anzahl an wiederverwendbaren Komonenten, um Listen, Grids und Datentabellen anzuzeigen. Falls du etwas möchtest, das spezifischer auf deine Anwendung zugeschnitten ist, kannst du auch deine eigene Windowing-Komponente erstellen, wie Twitter es getan hat.
Abgleiche vermeiden
React baut und pflegt eine interne Repräsentation der gerenderten Benutzeroberfläche. Sie beinhaltet die React-Elemente, die deine Komponenten zurückgeben. Diese Repräsentation erlaubt es React, zu vermeiden, neue DOM-Knoten zu erstellen oder auf bereits existierende zuzugreifen, wenn dies nicht nötig ist, da dies langsamer sein kann als Operationen an JavaScript-Objekten. Man spricht manchmal vom “virtuellen DOM”, aber in React Native funktioniert es auf die gleiche Weise.
Wenn Props oder State einer Komponente sich verändern, entscheidet React, ob ein tatsächliches DOM-Update nötig ist, indem es das neu ausgegebene Element mit dem zuvor gerenderten abgleicht. Wenn sie unterschiedlich sind, aktualisiert React das DOM.
Obwohl React nur DOM-Knoten aktualisiert, die sich verändert haben, braucht das erneute Rendern etwas Zeit. In vielen Fällen ist das kein Problem, doch wenn die Verlangsamung sich bemerkbar macht, kannst du den Prozess beschleunigen, indem du die Lifecycle-Funktion shouldComponentUpdate
, die vor jedem erneuten Rendern ausgelöst wird, überschreibst. Die Standard-Implementierung dieser Funktion gibt true
zurück und lässt React somit das Update durchführen:
shouldComponentUpdate(nextProps, nextState) {
return true;
}
Wenn du weißt, dass deine Komponente in manchen Fällen kein Update benötigt, kannst du stattdessen false
von shouldComponentUpdate
zurückgeben lassen, um den kompletten Rendering-Prozess zu überspringen, einschließlich des Aufrufs der render()
-Funktion auf diese und die darunterliegenden Komponenten.
In den meisten Fällen kannst du React.PureComponent
verwenden, statt shouldComponentUpdate()
von Hand zu schreiben. Es ist äquivalent zur Implementierung von shouldComponentUpdate()
mit einem flachen Vergleich von alten und neuen Props und State.
shouldComponentUpdate in Aktion
Hier ist ein Teilbaum von Komponenten. Für jede Komponente gibt SCU
an, was shouldComponentUpdate
zurückgegeben hat, und vDOMEq
gibt an, ob die gerenderten React-Elemente übereinstimmen. Die Farbe des Kreises zeigt schließlich, ob eine Komponente abgeglichen werden musste oder nicht.
Da shouldComponentUpdate
für den von C2 ausgehenden Teilbaum false
zurückgegeben hat, hat React nicht versucht, C2 zu rendern, und musste somit für C4 und C5 nicht einmal shouldComponentUpdate
aufrufen.
Für C1 und C3 hat shouldComponentUpdate
true
zurückgegeben, wodurch React zu den Blättern weiterwandern musste, um diese zu überprüfen. Für C6 hat shouldComponentUpdate
true
zurückgegeben, und da die gerenderten Elemente nicht übereinstimmten, musste React das DOM updaten.
Der letze interessante Fall ist C8. React musste diese Komponente rendern, aber da die zurückgegebenen React-Elemente keinen Unterschied zu den zuvor gerenderten hatte, musste es das DOM nicht updaten.
Beachte, dass React nur für C6 das DOM verändern musste, was unvermeidlich war. C8 war nach dem Vergleich der gerenderten React-Elemente abgehakt, und für C7 sowie den Teilbaum von C2 musste nicht einmal ein Vergleich stattfinden, da der Prozess hier schon nach shouldComponentUpdate
abgehakt war und somit render
nicht aufgerufen werden musste.
Beispiele
Wenn deine Komponente sich ausschließlich dann verändert, wenn die props.color
- oder die state.count
-Variable sich verändert, sollte shouldComponentUpdate
dies überprüfen:
class CounterButton extends React.Component {
constructor(props) {
super(props);
this.state = {count: 1};
}
shouldComponentUpdate(nextProps, nextState) {
if (this.props.color !== nextProps.color) {
return true;
}
if (this.state.count !== nextState.count) {
return true;
}
return false;
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
In diesem Code überprüft shouldComponentUpdate
lediglich, ob es eine Veränderung in props.color
oder state.count
gegeben hat. Wenn diese Werte sich nicht verändern, wird die Komponente nicht aktualisiert. Für den Fall, dass deine Komponente komplexer wird, könntest du ein ähnliches Muster verwenden, indem du einen “flachen Vergleich” zwischen allen Feldern von props
und state
machst, um festzustellen, ob die Komponente aktualisiert werden soll. Dieses Muster ist häufig genug, dass React einen Helfer zur Verfügung stellt, um diese Logik zu verwenden - durch Vererbung von React.PureComponent
. Dieser Code ist ein einfacherer Weg, dasselbe zu erreichen:
class CounterButton extends React.PureComponent {
constructor(props) {
super(props);
this.state = {count: 1};
}
render() {
return (
<button
color={this.props.color}
onClick={() => this.setState(state => ({count: state.count + 1}))}>
Count: {this.state.count}
</button>
);
}
}
In den meisten Fällen kannst du React.PureComponent
verwenden, statt dein eigenes shouldComponentUpdate
zu schreiben. Es führt nur einen flachen Vergleich durch, also kannst du React.PureComponent
nicht benutzen, wenn Props oder State sich auf eine Weise verändert haben, die ein flacher Vergleich übersehen würde.
Das kann bei komplexen Datenstrukturen ein Problem sein. Lass uns z. B. sagen, du möchtest eine ListOfWords
-Komponente, die eine komma-separierte Wörterliste rendert, mit einer Eltern-WorldAdder
-Komponente, die dich einen Button klicken lässt, um ein Wort zur Liste zu addieren. Dieser Code funktioniert nicht korrekt:
class ListOfWords extends React.PureComponent {
render() {
return <div>{this.props.words.join(',')}</div>;
}
}
class WordAdder extends React.Component {
constructor(props) {
super(props);
this.state = {
words: ['marklar']
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// Dieser Abschnitt hat einen schlechten Stil und ruft einen Bug hervor
const words = this.state.words;
words.push('marklar');
this.setState({words: words});
}
render() {
return (
<div>
<button onClick={this.handleClick} />
<ListOfWords words={this.state.words} />
</div>
);
}
}
Das Problem ist, dass PureComponent
einen einfachen Vergleich zwischen den alten und neuen Werten von this.props.words
durchführt. Da dieser Code das words
-Array in der handleClick
-Methode von WordAdder
mutiert, wird ein Vergleich der alten und neuen Werte von this.props.words
diese als gleich befinden, obwohl die tatsächlichen Wörter im Array sich verändert haben. Die ListOfWords
-Komponente wird daher nicht upgedatet, obwohl sie neue Wörter enthält und gerendert werden sollte.
Die Macht, Daten nicht zu mutieren
Der einfachste Weg, dieses Problem zu umgehen, besteht darin, die Veränderung von Werten zu vermeiden, die du als Props oder State verwendest. Zum Beispiel könnte die handleClick
-Methode von oben unter Verwendung von concat
wie folgt umgeschrieben werden:
handleClick() {
this.setState(state => ({
words: state.words.concat(['marklar'])
}));
}
ES6 unterstützt die Spread-Syntax für Arrays, was das erleichtern kann. Solltest du Create React App verwenden, ist diese Syntax standardmäßig verfügbar.
handleClick() {
this.setState(state => ({
words: [...state.words, 'marklar'],
}));
};
Du kannst auch Code, der Objekte verändert, in ähnlicher Weise umschreiben. Lass uns z. B. sagen, wir haben ein Objekt namens colormap
und möchten eine Funktion schreiben, die colormap.blue
in blue
umändert. Wir könnten schreiben:
function updateColorMap(colormap) {
colormap.right = 'blue';
}
Um dies zu schreiben, ohne das Original-Objekt zu verändern, können wir die Object.assign-Methode verwenden:
function updateColorMap(colormap) {
return Object.assign({}, colormap, {right: 'blue'});
}
updateColorMap
gibt nun ein neues Objekt zurück, statt das alte zu ändern. Object.assign
ist ES6 und benötigt ein Polyfill.
Object Spread-Syntax, macht es einfacher, Objekte auch ohne Veränderung zu aktualisieren.
function updateColorMap(colormap) {
return {...colormap, right: 'blue'};
}
Dieses Funktion wurde JavaScript in ES2018 hinzugefügt.
Solltest du Create React App verwenden, sind sowohl Object.assign
als auch die Object Spread-Syntax standardmäßig verfügbar.
Wenn du mit tief verschachtelten Objekten zu tun hast, kann sich das Aktualisieren des unveränderbaren Objektes, verworren anfühlen. Wenn du auf dieses Problem stößt, schaue dir Immer oder immutability-helper an. Mit diesen Bibliotheken kannst du gut lesbaren Code schreiben, ohne die Vorteile der Unveränderlichkeit (engl. immutability) zu verlieren.