React ist beliebt, beliebt genug, um eine faire Portion Kritik zu erhalten. Dennoch ist diese Kritik an React nicht völlig unbegründet: React und ReactDOM ergeben zusammen etwa 120 KiB minifizierten JavaScripts, was definitiv zu einer langsamen Startzeit beiträgt. Wenn man sich vollständig auf das Client-seitige Rendering in React verlässt, ruckelt es. Selbst wenn Sie Komponenten auf dem Server rendern und sie auf dem Client hydrieren, ruckelt es immer noch, da die Komponenten-Hydration rechenintensiv ist.
React hat zweifellos seinen Platz, wenn es um Anwendungen geht, die ein komplexes Zustandsmanagement erfordern, aber meiner Berufserfahrung nach gehört es in den meisten Szenarien, in denen ich es im Einsatz sehe, nicht hin. Wenn selbst ein bisschen React auf langsamen und schnellen Geräten gleichermaßen problematisch sein kann, ist die Verwendung eine bewusste Entscheidung, die Menschen mit Low-End-Hardware effektiv ausschließt.
Wenn es so klingt, als hätte ich einen Groll gegen React, dann muss ich gestehen, dass ich sein Komponentensierungsmodell wirklich mag. Es macht die Organisation von Code einfacher. Ich halte JSX für großartig. Server-Rendering ist auch cool – auch wenn das nur die Art und Weise ist, wie wir heutzutage „HTML über das Netzwerk senden“ sagen.
Dennoch, auch wenn ich gerne React-Komponenten auf dem Server verwende (oder Preact, wie ich es bevorzuge), ist es eine Herausforderung herauszufinden, wann es angebracht ist, sie auf dem Client zu verwenden. Im Folgenden finden Sie meine Erkenntnisse zur React-Leistung, während ich versucht habe, diese Herausforderung auf eine Weise zu meistern, die für die Benutzer am besten ist.
Die Szene setzen
In letzter Zeit habe ich an einem RSS-Feed-App-Nebenprojekt namens bylines.fyi gearbeitet. Diese App verwendet JavaScript sowohl im Back- als auch im Front-End. Ich glaube nicht, dass Client-seitige Frameworks abscheuliche Dinge sind, aber ich habe in meinen täglichen Arbeiten und Recherchen häufig zwei Dinge über die Implementierungen von Client-seitigen Frameworks beobachtet
- Frameworks haben das Potenzial, ein tieferes Verständnis der Dinge, die sie abstrahieren, nämlich der Webplattform, zu behindern. Ohne zumindest einige der Low-Level-APIs zu kennen, auf die Frameworks angewiesen sind, können wir nicht wissen, welche Projekte von einem Framework profitieren und welche Projekte besser ohne auskommen.
- Frameworks bieten nicht immer einen klaren Weg zu guten Benutzererlebnissen.
Sie mögen argumentieren können, dass mein erster Punkt gültig ist, aber der zweite Punkt wird immer schwieriger zu widerlegen. Sie erinnern sich vielleicht, dass Tim Kadlec vor einiger Zeit einige Nachforschungen über HTTPArchive zur Leistung von Web-Frameworks durchgeführt hat und zu dem Schluss kam, dass React keine herausragende Leistung bietet.
Dennoch wollte ich sehen, ob es möglich wäre, das, was ich für das Beste an React hielt, auf dem Server zu nutzen und gleichzeitig seine negativen Auswirkungen auf dem Client zu mildern. Für mich macht es Sinn, gleichzeitig ein Framework nutzen zu wollen, um meinen Code zu organisieren, aber auch dessen negativen Einfluss auf das Benutzererlebnis einzuschränken. Das erforderte ein wenig Experimentieren, um zu sehen, welcher Ansatz für meine App am besten geeignet wäre.
Das Experiment
Ich sorge dafür, jede von mir verwendete Komponente auf dem Server zu rendern, da ich glaube, dass die Last der Bereitstellung von Markup vom Server der Web-App getragen werden sollte, nicht vom Gerät des Benutzers. Ich brauchte jedoch *etwas* JavaScript in meiner RSS-Feed-App, um eine umschaltbare mobile Navigation zum Laufen zu bringen.

Dieses Szenario beschreibt treffend, was ich als *einfachen Zustand* bezeichne. Meiner Erfahrung nach sind lineare A-zu-B-Interaktionen ein Paradebeispiel für einfachen Zustand. Wir schalten eine Sache ein und dann wieder aus. Stateful, aber *einfach*.
Leider sehe ich oft stateful React-Komponenten zur Verwaltung von einfachem Zustand verwendet, was ein Kompromiss ist, der problematisch für die Leistung ist. Obwohl das im Moment eine vage Aussage sein mag, werden Sie im weiteren Verlauf feststellen. Das gesagt, ist es wichtig zu betonen, dass dies ein triviales Beispiel ist, aber es ist auch ein Kanarienvogel. Die meisten Entwickler werden – das *hoffe* ich – sich nicht allein auf React verlassen, um ein solches einfaches Verhalten für nur eine Sache auf ihrer Website zu steuern. Daher ist es wichtig zu verstehen, dass die Ergebnisse, die Sie sehen werden, dazu dienen sollen, Sie darüber zu informieren, *wie* Sie Ihre Anwendungen architekturieren, und wie sich die Auswirkungen Ihrer Framework-Entscheidungen auf die Laufzeitleistung auswirken könnten.
Die Bedingungen
Meine RSS-Feed-App befindet sich noch in der Entwicklung. Sie enthält keinen Fremdcode, was das Testen in einer ruhigen Umgebung erleichtert. Das durchgeführte Experiment verglich das Verhalten des mobilen Navigations-Toggles über drei Implementierungen hinweg
- Eine stateful React-Komponente (
React.Component), die auf dem Server gerendert und auf dem Client hydriert wurde. - Eine stateful Preact-Komponente, ebenfalls serverseitig gerendert und auf dem Client hydriert.
- Eine serverseitig gerenderte, stateless Preact-Komponente, die nicht hydriert wurde. Stattdessen sorgen reguläre Event-Listener auf dem Client für die Funktionalität der mobilen Navigation.
Jedes dieser Szenarien wurde in vier verschiedenen Umgebungen gemessen
- Ein Nokia 2 Android-Telefon mit Chrome 83.
- Ein ASUS X550CC Laptop von 2013 mit Windows 10 und Chrome 83.
- Ein altes iPhone SE erster Generation mit Safari 13.
- Ein neues iPhone SE zweiter Generation, ebenfalls mit Safari 13.
Ich glaube, dass diese Bandbreite an mobiler Hardware die Leistung über ein breites Spektrum von Gerätefähigkeiten hinweg veranschaulichen wird, auch wenn sie auf der Apple-Seite etwas schwer ist.
Was wurde gemessen
Ich wollte vier Dinge für jede Implementierung in jeder Umgebung messen
- Startzeit. Für React und Preact umfasste dies die Zeit, die zum Laden des Framework-Codes sowie zur Hydration der Komponente auf dem Client benötigt wurde. Für das Event-Listener-Szenario umfasste dies nur den Event-Listener-Code selbst.
- Hydrationszeit. Für die React- und Preact-Szenarien ist dies eine Teilmenge der Startzeit. Aufgrund von Problemen mit dem Absturz des Remote-Debuggings in Safari unter macOS konnte ich die Hydrationszeit auf iOS-Geräten nicht allein messen. Event-Listener-Implementierungen hatten keine Hydrationskosten.
- Zeit zum Öffnen der mobilen Navigation. Dies gibt uns Aufschluss darüber, wie viel Overhead Frameworks bei der Abstraktion von Event-Handlern einführen und wie sich das im Vergleich zum frameworklosen Ansatz verhält.
- Zeit zum Schließen der mobilen Navigation. Wie sich herausstellte, war dies deutlich weniger als die Kosten für das Öffnen des Menüs. Ich habe mich letztendlich entschieden, diese Zahlen nicht in diesem Artikel zu veröffentlichen.
Es sollte beachtet werden, dass die Messungen dieser Verhaltensweisen *nur* Skripting-Zeit beinhalten. Jegliche Layout-, Paint- und Compositing-Kosten wären zusätzlich zu diesen Messungen und außerhalb dieser. Man sollte darauf achten, sich daran zu erinnern, dass diese Aktivitäten um die Haupt-Thread-Zeit konkurrieren, zusammen mit den Skripten, die sie auslösen.
Das Verfahren
Um jede der drei Implementierungen der mobilen Navigation auf jedem Gerät zu testen, habe ich dieses Verfahren befolgt
- Ich habe Remote-Debugging in Chrome unter macOS für das Nokia 2 verwendet. Für iPhones habe ich das Äquivalent von Safari zum Remote-Debugging verwendet.
- Ich habe die RSS-Feed-App, die in meinem lokalen Netzwerk läuft, auf jedem Gerät zu derselben Seite aufgerufen, auf der der Code zum Umschalten der mobilen Navigation ausgeführt werden konnte. Aus diesem Grund spielte die Netzwerkleistung bei meinen Messungen *keine* Rolle.
- Ohne CPU- oder Netzwerkdrosselung begann ich, im Profiler aufzuzeichnen, und lud die Seite neu.
- Nach dem Seitenaufruf öffnete ich die mobile Navigation und schloss sie dann wieder.
- Ich stoppte den Profiler und zeichnete auf, wie viel CPU-Zeit für jedes der vier zuvor genannten Verhaltensweisen benötigt wurde.
- Ich löschte die Performance-Timeline. In Chrome klickte ich auch auf die Schaltfläche zur Speicherbereinigung, um jeden Arbeitsspeicher freizugeben, der durch den App-Code einer früheren Sitzung belegt gewesen sein könnte.
Ich wiederholte dieses Verfahren zehnmal für jedes Szenario auf jedem Gerät. Zehn Iterationen schienen gerade genug Daten zu liefern, um einige Ausreißer zu erkennen und ein einigermaßen genaues Bild zu erhalten, aber ich überlasse Ihnen die Entscheidung, während wir die Ergebnisse durchgehen. Wenn Sie kein Wortspiel mit meinen Erkenntnissen wünschen, können Sie die Ergebnisse in dieser Tabelle einsehen und Ihre eigenen Schlüsse ziehen, sowie den mobilen Navigationscode für jede Implementierung.
Die Ergebnisse
Ich wollte diese Informationen ursprünglich in einem Diagramm darstellen, aber aufgrund der Komplexität dessen, was ich gemessen habe, war ich mir nicht sicher, wie ich die Ergebnisse darstellen sollte, ohne die Visualisierung zu überladen. Daher präsentiere ich die minimalen, maximalen, medianen und durchschnittlichen CPU-Zeiten in einer Reihe von Tabellen, die alle effektiv die Bandbreite der Ergebnisse veranschaulichen, auf die ich bei jedem Test gestoßen bin.
Google Chrome auf Nokia 2
Das Nokia 2 ist ein kostengünstiges Android-Gerät mit einem ARM Cortex-A7 Prozessor. Es ist *kein* Kraftpaket, sondern ein billiges und leicht erhältliches Gerät. Die Android-Nutzung weltweit liegt derzeit bei etwa 40 %, und obwohl die Spezifikationen von Android-Geräten stark variieren, sind Low-End-Android-Geräte nicht selten. Dies ist ein Problem, das wir als eines sowohl von Reichtum *als auch* von der Nähe zu schneller Netzwerkinfrastruktur erkennen müssen.
Sehen wir uns die Zahlen für die Startkosten an.
Startzeit
| React Komponente | Preact Komponente | addEventListener Code | |
|---|---|---|---|
| Min | 137.21 | 31.23 | 4.69 |
| Median | 147.76 | 42.06 | 5.99 |
| Avg | 162.73 | 43.16 | 6.81 |
| Max | 280.81 | 62.03 | 12.06 |
Ich glaube, es sagt einiges aus, dass es im Durchschnitt über 160 ms dauert, um React zu parsen und zu kompilieren und *eine Komponente* zu hydrieren. Zur Erinnerung: Die *Startkosten* beinhalten in diesem Fall die Zeit, die der Browser benötigt, um die für die mobile Navigation benötigten Skripte auszuwerten. Für React und Preact beinhaltet dies auch die Hydrationszeit, die in beiden Fällen zu dem Uncanny-Valley-Effekt beitragen kann, den wir manchmal beim Start erleben.
Preact schneidet deutlich besser ab und benötigt etwa 73 % weniger Zeit als React, was angesichts der geringen Größe von Preact (10 KiB unkomprimiert) sinnvoll ist. Dennoch ist es wichtig zu beachten, dass das Frame-Budget in Chrome etwa 10 ms beträgt, um Ruckeln bei 60 fps zu vermeiden. Janky-Start ist genauso schlimm wie alles andere Janky und ist ein Faktor bei der Berechnung des First Input Delay. Alles in allem schneidet Preact jedoch relativ gut ab.
Was die addEventListener-Implementierung betrifft, so stellt sich heraus, dass die Parse- und Kompilierzeit für ein kleines Skript ohne Overhead erwartungsgemäß sehr gering ist. Selbst bei der maximalen Stichprobenzeit von 12 ms befinden Sie sich kaum im äußeren Ring des Ballungsraums Janksburg. Schauen wir uns nun die reine Hydrationskosten an.
Hydrationszeit
| React Komponente | Preact Komponente | |
|---|---|---|
| Min | 67.04 | 19.17 |
| Median | 70.33 | 26.91 |
| Avg | 74.87 | 26.77 |
| Max | 117.86 | 44.62 |
Für React liegt dies immer noch in der Nähe des Yikes Peak. Sicher, eine mittlere Hydrationszeit von 70 ms für *eine* Komponente ist kein großes Problem, aber denken Sie darüber nach, wie sich die Hydrationskosten skalieren, wenn Sie eine *Vielzahl* von Komponenten auf derselben Seite haben. Es ist keine Überraschung, dass sich die React-Websites, die ich auf diesem Gerät teste, eher wie Ausdauertests als wie Benutzererlebnisse anfühlen.
Die Hydrationszeiten von Preact sind deutlich kürzer, was sinnvoll ist, da die Dokumentation von Preact für seine hydrate-Methode besagt, dass sie „die meisten Vergleiche überspringt und dennoch Event-Listener anhängt und Ihren Komponentebaum einrichtet“. Die Hydrationszeit für das Szenario addEventListener wird nicht gemeldet, da Hydration bei VDOM-Frameworks keine Rolle spielt. Als Nächstes werfen wir einen Blick auf die Zeit, die zum Öffnen der mobilen Navigation benötigt wird.
Zeit zum Öffnen der mobilen Navigation
| React Komponente | Preact Komponente | addEventListener Code | |
|---|---|---|---|
| Min | 30.89 | 11.94 | 3.94 |
| Median | 43.62 | 14.29 | 6.14 |
| Avg | 43.16 | 14.66 | 6.12 |
| Max | 53.19 | 20.46 | 8.60 |
Ich finde diese Zahlen *etwas* überraschend, da React fast siebenmal so viel CPU-Zeit benötigt, um einen Event-Listener-Callback auszuführen, als ein von Ihnen selbst registrierter Event-Listener. Das ist verständlich, da die Zustandsmanagementlogik von React notwendiger Overhead ist, aber man muss sich fragen, ob es sich für simple, lineare Interaktionen lohnt.
Auf der anderen Seite schafft es Preact, den Overhead bei Event-Listenern so weit zu begrenzen, dass die Ausführung eines Event-Listener-Callbacks nur doppelt so viel CPU-Zeit benötigt.
Die CPU-Zeit, die zum Schließen der mobilen Navigation benötigt wurde, war deutlich geringer, mit einer durchschnittlichen Zeit von etwa 16,5 ms für React, wobei Preact und reine Event-Listener etwa 11 ms bzw. 6 ms benötigten. Ich würde die vollständige Tabelle für die Messungen zum Schließen der mobilen Navigation posten, aber wir haben noch viel zu durchforsten. Außerdem können Sie sich diese Zahlen selbst in der bereits erwähnten Tabelle ansehen.
Ein kurzer Hinweis zu JavaScript-Samples
Bevor wir zu den iOS-Ergebnissen übergehen, möchte ich einen potenziellen Stolperstein ansprechen: die Auswirkungen des Deaktivierens von JavaScript-Samples in den Chrome DevTools beim Aufzeichnen von Sitzungen auf Remote-Geräten. Nach der Zusammenfassung meiner anfänglichen Ergebnisse fragte ich mich, ob der Overhead der Erfassung ganzer Aufrufstapel meine Ergebnisse verzerrte, also testete ich das React-Szenario erneut mit deaktivierten Samples. Wie sich herausstellte, hatte diese Einstellung keine signifikante Auswirkung auf die Ergebnisse.
Da die Aufrufstapel abgeschnitten waren, konnte ich die Komponenten-Hydrationszeit nicht messen. Die durchschnittlichen Startkosten mit deaktivierten Samples im Vergleich zu aktivierten Samples betrugen 160,74 ms und 162,73 ms. Die jeweiligen Medianwerte lagen bei 157,81 ms und 147,76 ms. Ich würde dies als eindeutig „im Rauschen“ bezeichnen.
Safari auf iPhone SE erster Generation
Das ursprüngliche iPhone SE ist ein großartiges Telefon. Trotz seines Alters erfreut es sich immer noch treuer Besitzer aufgrund seiner angenehmeren physischen Größe. Es wurde mit dem Apple A9 Prozessor ausgeliefert, der immer noch ein solider Konkurrent ist. Sehen wir uns an, wie es bei der Startzeit abgeschnitten hat.
Startzeit
| React Komponente | Preact Komponente | addEventListener Code | |
|---|---|---|---|
| Min | 32.06 | 7.63 | 0.81 |
| Median | 35.60 | 9.42 | 1.02 |
| Avg | 35.76 | 10.15 | 1.07 |
| Max | 39.18 | 16.94 | 1.56 |
Dies ist eine deutliche Verbesserung gegenüber dem Nokia 2 und veranschaulicht die Kluft zwischen Low-End-Android-Geräten und älteren Apple-Geräten mit erheblicher Laufleistung.
Die Leistung von React ist immer noch nicht gut, aber Preact bringt uns in ein typisches Frame-Budget für Chrome. Event-Listener allein sind natürlich blitzschnell und lassen viel Raum im Frame-Budget für andere Aktivitäten.
Leider konnte ich die Hydrationszeiten auf dem iPhone nicht messen, da die Remote-Debugging-Sitzung jedes Mal abstürzte, wenn ich den Aufrufstapel in den DevTools von Safari durchlief. Da die Hydrationszeit eine Teilmenge der gesamten Startkosten war, können Sie davon ausgehen, dass sie mindestens die Hälfte der Startzeit ausmacht, wenn die Ergebnisse der Nokia 2-Tests ein Indikator sind.
Zeit zum Öffnen der mobilen Navigation
| React Komponente | Preact Komponente | addEventListener Code | |
|---|---|---|---|
| Min | 16.91 | 5.45 | 0.48 |
| Median | 21.11 | 8.62 | 0.50 |
| Avg | 21.09 | 11.07 | 0.56 |
| Max | 24.20 | 19.79 | 1.00 |
React schneidet hier ganz gut ab, aber Preact scheint Event-Listener etwas effizienter zu handhaben. Reine Event-Listener sind blitzschnell, selbst auf diesem alten iPhone.
Safari auf iPhone SE zweiter Generation
Mitte 2020 kaufte ich das neue iPhone SE. Es hat die gleiche physische Größe wie ein iPhone 8 und ähnliche Telefone, aber der Prozessor ist derselbe Apple A13, der im iPhone 11 verwendet wird. Es ist *sehr* schnell für seinen relativ niedrigen Verkaufspreis von 400 US-Dollar. Angesichts eines solch leistungsstarken Prozessors, wie geht es damit um?
Startzeit
| React Komponente | Preact Komponente | addEventListener Code | |
|---|---|---|---|
| Min | 20.26 | 5.19 | 0.53 |
| Median | 22.20 | 6.48 | 0.69 |
| Avg | 22.02 | 6.36 | 0.68 |
| Max | 23.67 | 7.18 | 0.88 |
Ich nehme an, irgendwann gibt es abnehmende Erträge, wenn es um die relativ geringe Arbeitslast des Ladens eines einzelnen Frameworks und des Hydrierens einer Komponente geht. Die Dinge sind auf einem iPhone SE der zweiten Generation in einigen Fällen etwas schneller als bei seinem Vorgängermodell, aber nicht dramatisch. Ich stelle mir vor, dass dieses Telefon größere und anhaltendere Workloads besser bewältigen würde als sein Vorgänger.
Zeit zum Öffnen der mobilen Navigation
| React Komponente | Preact Komponente | addEventListener Code | |
|---|---|---|---|
| Min | 13.15 | 12.06 | 0.49 |
| Median | 16.41 | 12.57 | 0.53 |
| Avg | 16.11 | 12.63 | 0.56 |
| Max | 17.51 | 13.26 | 0.78 |
Leichte Verbesserung der React-Leistung hier, aber nicht viel mehr. Seltsamerweise scheint Preact auf diesem Gerät im Durchschnitt länger zu brauchen, um die mobile Navigation zu öffnen, als sein Vorgängermodell, aber das schreibe ich Ausreißern zu, die einen relativ kleinen Datensatz verzerren. Ich würde keinesfalls davon ausgehen, dass das iPhone SE der ersten Generation auf dieser Basis ein schnelleres Gerät ist.
Chrome auf einem veralteten Windows 10 Laptop
Zugegebenermaßen waren dies die Ergebnisse, auf die ich am meisten gespannt war: Wie bewältigt ein ASUS-Laptop von 2013 mit Windows 10 und einem damals aktuellen Ivy Bridge i5 diese Dinge?
Startzeit
| React Komponente | Preact Komponente | addEventListener Code | |
|---|---|---|---|
| Min | 43.15 | 13.11 | 1.81 |
| Median | 45.95 | 14.54 | 2.03 |
| Avg | 45.92 | 14.47 | 2.39 |
| Max | 48.98 | 16.49 | 3.61 |
Die Zahlen sind nicht schlecht, wenn man bedenkt, dass das Gerät sieben Jahre alt ist. Der Ivy Bridge i5 war ein guter Prozessor für seine Zeit, und wenn man ihn mit der Tatsache kombiniert, dass er *aktiv gekühlt* wird (im Gegensatz zur *passiven Kühlung* von Prozessoren mobiler Geräte), stößt er wahrscheinlich nicht so oft auf Thermal-Throttling-Szenarien wie mobile Geräte.
Hydrationszeit
| React Komponente | Preact Komponente | |
|---|---|---|
| Min | 17.75 | 7.64 |
| Median | 23.55 | 8.73 |
| Avg | 23.12 | 8.72 |
| Max | 26.25 | 9.55 |
Preact schneidet hier gut ab, bleibt im Frame-Budget von Chrome und ist fast dreimal schneller als React. Die Situation könnte bei zehn Komponenten, die beim Start geladen werden, ganz anders aussehen, möglicherweise sogar bei Preact.
Zeit zum Öffnen der mobilen Navigation
| Preact Komponente | addEventListener Code | ||
|---|---|---|---|
| Min | 6.06 | 2.50 | 0.88 |
| Median | 10.43 | 3.09 | 0.97 |
| Avg | 11.24 | 3.21 | 1.02 |
| Max | 14.44 | 4.34 | 1.49 |
Bei dieser isolierten Interaktion sehen wir eine Leistung, die mit High-End-Mobilgeräten vergleichbar ist. Es ist ermutigend zu sehen, wie ein so alter Laptop noch einigermaßen mithält. Allerdings springt der Lüfter dieses Laptops beim Surfen im Internet oft an, sodass die aktive Kühlung wahrscheinlich die Rettung dieses Geräts ist. Wenn der i5 dieses Geräts passiv gekühlt würde, vermute ich, dass seine Leistung sinken könnte.
Flache Aufrufstapel für den Sieg
Es ist kein Geheimnis, warum React und Preact länger zum Starten brauchen als eine Lösung, die Frameworks ganz vermeidet. Weniger Arbeit bedeutet weniger Verarbeitungszeit.
Obwohl ich die Startzeit für entscheidend halte, ist es wahrscheinlich unvermeidlich, dass man *einiges* an Geschwindigkeit für eine bessere Entwicklererfahrung eintauscht. Ich würde jedoch nachdrücklich argumentieren, dass wir viel zu oft zu viel in Richtung Entwicklererfahrung als Benutzererfahrung eintauschen.
Die Drachen liegen auch darin, was wir *nachdem* das Framework geladen ist, tun. Die Client-seitige Hydration ist etwas, das meiner Meinung nach viel zu oft missbraucht wird und manchmal völlig unnötig sein kann. Jedes Mal, wenn Sie eine Komponente in React hydrieren, werfen Sie dies auf den Haupt-Thread

Erinnern Sie sich, dass auf dem Nokia 2 die *minimale* Zeit, die ich für die Hydration der mobilen Navigationskomponente gemessen habe, etwa 67 ms betrug. In Preact – für das Sie den Hydrations-Aufrufstapel unten sehen werden – dauert es etwa 20 ms.

Diese beiden Aufrufstapel sind nicht auf derselben Skala, aber die Hydrationslogik von Preact ist vereinfacht, wahrscheinlich weil „die meisten Vergleiche übersprungen werden“, wie die Dokumentation von Preact besagt. Hier passiert deutlich weniger. Wenn Sie dem Metall näher kommen, indem Sie addEventListener anstelle eines Frameworks verwenden, können Sie noch schneller werden.

Nicht jede Situation erfordert diesen Ansatz, aber Sie werden überrascht sein, was Sie erreichen können, wenn Ihre Werkzeuge addEventListener, querySelector, classList, setAttribute/getAttribute und so weiter sind.
Diese Methoden – und viele weitere ähnliche – sind das, worauf Frameworks selbst angewiesen sind. Der Trick besteht darin, zu bewerten, welche Funktionalität Sie sicher außerhalb dessen liefern können, was das Framework bietet, und sich auf das Framework zu verlassen, wenn es sinnvoll ist.

Wenn dies ein Aufrufstapel wäre, um beispielsweise Daten für eine API-Anfrage auf dem Client abzurufen und den komplexen Zustand der Benutzeroberfläche in dieser Situation zu verwalten, würde ich diese Kosten als akzeptabler empfinden. Aber das ist nicht der Fall. Wir lassen nur eine Navigation auf dem Bildschirm erscheinen, wenn der Benutzer auf eine Schaltfläche tippt. Es ist, als würde man einen Bulldozer verwenden, wenn eine Schaufel besser für den Job geeignet wäre.
Preact schlägt zumindest die goldene Mitte

Preact benötigt etwa ein Drittel der Zeit, um die gleiche Arbeit wie React zu erledigen. Aber auf diesem leistungsschwachen Gerät überschreitet es oft das Frame-Budget. Das bedeutet, dass das Öffnen dieser Navigation auf einigen Geräten träge animiert wird, da die Layout- und Zeichenarbeiten möglicherweise nicht genügend Zeit haben, um fertig zu werden, ohne in den Bereich der Long Tasks zu geraten.

In diesem Fall war ein Event-Listener das, was ich brauchte. Er erledigt die Aufgabe auf dem leistungsschwachen Gerät siebenmal schneller als React.
Fazit
Dies ist kein Front gegen React, sondern eher ein Plädoyer für die Berücksichtigung unserer Arbeitsweise. Einige dieser Performance-Fallstricke können vermieden werden, wenn wir sorgfältig prüfen, welche Werkzeuge für die jeweilige Aufgabe sinnvoll sind, selbst für Anwendungen mit viel komplexer Interaktivität. Um fair zu React zu sein, existieren diese Fallstricke wahrscheinlich in vielen VDOM-Frameworks, da ihre Natur notwendige Overhead-Kosten mit sich bringt, um all diese Dinge für uns zu verwalten.
Selbst wenn Sie an etwas arbeiten, das kein React oder Preact erfordert, aber Sie die Vorteile der Komponentisierung nutzen möchten, sollten Sie in Erwägung ziehen, alles zunächst auf dem Server zu belassen. Dieser Ansatz bedeutet, dass Sie entscheiden können, ob und wann es angebracht ist, Funktionalität auf den Client auszulagern – und *wie* Sie dies tun werden.
Im Falle meiner RSS-Feed-App kann ich dies bewerkstelligen, indem ich leichtgewichtigen Event-Listener-Code in den Entry Point für diese Seite der App lege und ein Asset-Manifest verwende, um die minimale Menge an Skript einzubinden, die für die Funktionalität jeder Seite erforderlich ist.
Nehmen wir nun an, Sie haben eine Anwendung, die *wirklich* das benötigt, was React bietet. Sie haben komplexe Interaktivität mit viel Zustand. Hier sind einige Dinge, die Sie tun können, um zu versuchen, Dinge etwas schneller zum Laufen zu bringen.
- Überprüfen Sie alle Ihre zustandsbehafteten Komponenten – also jede Komponente, die von
React.Componenterbt – und sehen Sie, ob sie als zustandslose Komponenten refaktorisiert werden können. Wenn eine Komponente keine Lifecycle-Methoden oder Zustand verwendet, können Sie sie als zustandlos refaktorieren. - Vermeiden Sie dann, wenn möglich, das Senden von JavaScript an den Client für diese zustandlosen Komponenten sowie deren Hydration. Wenn eine Komponente zustandlos ist, rendern Sie sie nur auf dem Server. Rendern Sie Komponenten vorab, wann immer möglich, um die Serverantwortzeit zu minimieren, da Server-Rendering eigene Performance-Fallstricke birgt.
- Wenn Sie eine zustandsbehaftete Komponente mit einfacher Interaktivität haben, erwägen Sie, diese Komponente vorab zu rendern/serverseitig zu rendern und ihre Interaktivität durch Framework-unabhängige Event-Listener zu ersetzen. Dies vermeidet die Hydration vollständig, und Benutzerinteraktionen müssen nicht durch die Zustandsverwaltungslogik des Frameworks gefiltert werden.
- Wenn Sie zustandsbehaftete Komponenten auf dem Client hydrieren müssen, erwägen Sie, Komponenten, die sich nicht am oberen Rand der Seite befinden, verzögert zu hydrieren. Ein Intersection Observer, der einen Callback auslöst, funktioniert hierfür sehr gut und gibt dem Hauptthread mehr Zeit für kritische Komponenten auf der Seite.
- Für verzögert zu hydrierende Komponenten prüfen Sie, ob Sie deren Hydration während der Hauptthread-Leerlaufzeit mit
requestIdleCallbackplanen können. - Wenn möglich, erwägen Sie, von React zu Preact zu wechseln. Angesichts der Tatsache, wie viel schneller es auf dem Client läuft als React, lohnt es sich, die Diskussion mit Ihrem Team zu führen, ob dies möglich ist. Die neueste Version von Preact ist für die meisten Dinge fast 1:1 mit React kompatibel, und
preact/compaterleichtert diesen Übergang hervorragend. Ich glaube nicht, dass Preact ein Allheilmittel für Performance ist, aber es bringt Sie näher dorthin, wo Sie hin müssen. - Berücksichtigen Sie die Anpassung Ihrer Benutzererfahrung an Benutzer mit wenig Gerätespeicher.
navigator.deviceMemory(verfügbar in Chrome und abgeleiteten Browsern) ermöglicht es Ihnen, die Benutzererfahrung für Benutzer auf Geräten mit wenig Speicher zu ändern. Wenn jemand ein solches Gerät hat, ist es wahrscheinlich, dass auch sein Prozessor nicht sehr schnell ist.
Was auch immer Sie mit diesen Informationen entscheiden, der Kern meines Arguments ist: Wenn Sie React oder eine VDOM-Bibliothek verwenden, sollten Sie einige Zeit damit verbringen, deren Auswirkungen auf verschiedene Geräte zu untersuchen. Besorgen Sie sich ein günstiges Android-Gerät und sehen Sie, wie sich Ihre App anfühlt. Vergleichen Sie diese Erfahrung mit Ihren High-End-Geräten.
Vor allem folgen Sie nicht den "Best Practices", wenn das Ergebnis ist, dass Ihre App einen Teil Ihres Publikums, das sich keine High-End-Geräte leisten kann, effektiv ausschließt. Drängen Sie weiterhin darauf, dass alles schneller wird. Wenn unsere tägliche Arbeit irgendeinen Hinweis gibt, ist dies ein Unterfangen, das Sie eine Weile beschäftigen wird, aber das ist in Ordnung. Das Web schneller zu machen, macht das Web an mehr Orten zugänglicher. Das Web zugänglicher zu machen, macht das Web *inklusiver*. Das ist die wirklich gute Arbeit, die wir alle mit aller Kraft tun sollten.
Ich möchte Eric Bailey für sein redaktionelles Feedback zu diesem Beitrag sowie dem CSS-Tricks-Team für seine Bereitschaft, ihn zu veröffentlichen, meinen Dank aussprechen.
Das war ein ausgezeichneter Beitrag!
Ich stimme voll und ganz zu, dass Optimierung im Jahr 2020 Priorität haben sollte.
Aber die Frage, die wir uns (Entwickler) stellen sollten, ist: **Wer ist wofür verantwortlich?**
So ziemlich jede Editorial, die man heutzutage liest, ermutigt einen, seinen Code zu optimieren und stark in Barrierefreiheit zu investieren. Und die Art, wie sie geschrieben sind, legt diese Last auf den gewöhnlichen Entwickler.
Aus meiner beruflichen Erfahrung heraus, und es tut mir leid, brutal zu sein, sind die meisten gewöhnlichen Entwickler einfach schlecht. Sie haben Schwierigkeiten, Code zu schreiben, der leicht zu lesen und optimiert ist. Ihnen fehlt schlichtweg die Vision des Gesamtsystems, sie tun, was ihnen gesagt wird, und das war's. Es ist traurig, aber es ist die Realität...
Verstehen Sie mich nicht falsch, ich möchte, dass meine Websites/Apps so schnell und barrierefrei wie möglich sind, aber **wäre es nicht fair, von Browser-, Werkzeug- und Framework-Herstellern die meiste Schwerstarbeit zu verlangen?**
Die uns zur Verfügung stehenden Werkzeuge haben in den letzten Jahren unglaubliche Fortschritte gemacht, aber sie haben immer noch einen weiten Weg vor sich.
Browser sind die erste Verteidigungslinie, und es ist weit davon entfernt, rosig zu sein.
Wann haben Sie das letzte Mal den grundlegenden Datumsauswähler wegen seiner Barrierefreiheit nutzen können? Und wenn es jemals einen gut zugänglichen Standard-Datumsselektor gab, was ich bezweifle, sah er dann gut genug aus, um ernst genommen zu werden?
Wäre es nicht fair zu sagen, dass der Mangel an CSS-Anpassungsmöglichkeiten für das grundlegende Element die direkte Ursache für wahrscheinlich 25 % aller Barrierefreiheitsprobleme im heutigen Web ist? Entwickler werden gezwungen, benutzerdefinierte Lösungen zu verwenden, die alle in der einen oder anderen Weise Mängel aufweisen. Geben Sie mir ein ordentliches, und ich werde es benutzen, aber ich mache mir keine Hoffnungen...
Es gibt eine lange Liste von Dingen, die Entwickler seit vielen Jahren brauchen. Man könnte sogar sagen, wir sind gerade erst aus dem finsteren Mittelalter herausgekommen, da Browser weitgehend funktionsgleich sind. Es ist höchste Zeit, dass Browserhersteller und die Verantwortlichen für HTML, JS, Spezifikationen usw. diese Liste tatsächlich durchgehen.
Frameworks sind ein etwas kontroverses Thema.
Persönlich, nachdem ich auf beiden Seiten gelebt habe (mit und ohne sie), wäre es sehr, sehr, sehr... sehr schwer, zu einem Nicht-Framework-Ansatz zurückzukehren. Komponenten, Komposition und Zustände sind zu nützlich geworden, um sie wegzulegen.
In einer Unternehmensumgebung sind Frameworks quasi eine Notwendigkeit. Nicht, weil es ohne sie nicht geht, sondern weil es Zeit kostet, es selbst zu tun, und Zeit Geld ist. Ob man es mag oder nicht, Geld ist der primäre Faktor bei den meisten Entscheidungen in einem Unternehmen, und das ist es, was die meisten gewöhnlichen Entwickler tun: in einem Unternehmen arbeiten... Nicht nur das, sondern Frameworks bieten Richtlinien und begrenzen etwas den potenziellen Schaden, den ein Entwickler anrichten könnte. Dinge werden schneller und mit weniger Fehlern erledigt. Geld, darum geht es letztendlich.
Ob ein Framework optimiert ist oder nicht, ist die Verantwortung einer sehr kleinen Anzahl von Entwicklern. Die Entscheidung, ein Framework gegenüber einem anderen zu verwenden, wird auf einer höheren Ebene in Ihrer Organisation getroffen, normalerweise von kompetenten Leuten, und der Rest der Entwickler benutzt einfach, was ihnen gegeben wird.
Auch Werkzeuge spielen eine Rolle.
In letzter Zeit wurden Entwickler mit all den Optionen sehr verwöhnt. Aber es gibt noch einen langen Weg.
Barrierefreiheit ist in etwa 99,9 % der Werkzeuge meist nur ein nachträglicher Gedanke, was die Last auf die gewöhnlichen Entwickler legt, die Drecksarbeit selbst zu erledigen.
Ich persönlich liebe NextJs und ich liebe es, wie Preact es mir ermöglicht, viele KB aus dem JS-Bundle zu sparen. Ich liebe viele Dinge an zahlreichen Frameworks (React, Vue, Svelte, Eleventy, Gatsby, etc.), aber ich weine auch, wenn ich sehe, wie schlecht sie manchmal optimiert sind.
Uns fehlt der Heilige Gral der Optimierung. Ich sollte meinen Code in dem von mir gewählten Framework schreiben können und mir keine Sorgen darüber machen müssen, wie er in HTML, CSS und JS transpiliert wird.
Die Frameworks, die wir heute verwenden, tun dies bis zu einem gewissen Grad, aber wir haben kein einziges Framework, das den gesamten HTML + CSS + JS-Stack von oben bis unten abdeckt.
Wenn ich CSS-In-JS verwende, möchte ich, dass das Tool den resultierenden Code nur das benötigte CSS mit keinem JS-Overhead lädt. Kapselung, Lazy Loading und Critical CSS sollten standardmäßig von mir genommen werden.
Wenn ich JS-Code schreibe, möchte ich mir keine Gedanken über die Größe des Bundles machen oder wie ich es aufteilen soll. Ich sollte mir keine Sorgen machen müssen, die Hydration für Below-the-Fold-Komponenten nicht anzuwenden. Ganz zu schweigen von node_modules mit seinen Milliarden von Dateien...
Barrierefreiheit in HTML sollte standardmäßig vorhanden sein, nicht auf eine sehr umständliche Weise eingepasst werden, wie es heute der Fall ist. Es sollte einfach sein, alle Ihre Dateien beim ersten Request an den Browser zu senden, die Leitungen zu öffnen, wir sind im Jahr 2020, Multithreading.
Ich bin gut, zumindest denke ich das ;) , aber ich will es nicht alles alleine machen. Ich habe ein Leben, es gibt nur 24 Stunden am Tag und ich schlafe gerne mindestens 7 Stunden pro Nacht. Können die Oberhäupter in der Nahrungskette von ihren Thronen steigen und uns hier unten helfen?
Mein Kommentar soll Ihren Artikel in keiner Weise schmälern. Ich stimme zu, dass jeder Entwickler sein sollte, seinen Code zu optimieren und die besten Entscheidungen nach seinen Bedürfnissen zu treffen. Ich bemühe mich jeden Tag darum.
Ich wollte lediglich ein Gefühl wiedergeben, das sich bei vielen Entwicklern breitmacht. Der Druck, der auf unseren Schultern lastet, nimmt ständig zu, und **ein Großteil davon könnte und sollte von Browser-, Werkzeug- und Framework-Herstellern gelindert werden**.
Ich ruhe meinen Fall ;)
Ich würde eher sagen, im Gegenteil, wir treten gerade tief in das dunkle Zeitalter der Browser ein, wobei Firefox praktisch tot ist und die Benutzerfreundlichkeit schrecklich ist. Und selbst wenn sich die Webplattform vorwärts zu bewegen scheint, verschlechtert sich die allgemeine UX ständig.
Vielen Dank für den Artikel!
Würden Sie erwägen, denselben Test mit einigen der VDOM-freien Frameworks wie Svelte oder viperHyper/hyperHtml durchzuführen? (Es gibt mehr, aber nicht, wenn einige davon "low-level" sind, d.h. unterstützt lit-html von sich aus das Hinzufügen von Event-Handlern.)
Hey Jonny, wenn alles von Google und Facebook in der Webwelt gemacht wird, warum sollte dich dann jemand einstellen? Wie erwartest du höhere Bezahlung für Web-Entwickler, wenn alles paletti ist? Wenn du Probleme mit Tools und Frameworks hast, baue eins und mache uns das Leben leichter.
Außerdem können Sie HTTP2 verwenden, um Ihre Dateien parallel zu pushen.
Ich denke, die Web-Community hat auch einen Punkt bezüglich "induzierter Nachfrage" gemacht. Je leistungsfähiger die Werkzeuge sind, desto wahrscheinlicher ist es, dass sie von Entwicklern missbraucht werden. Daher glaube ich, dass es wichtig ist, Entwickler über Performance und Barrierefreiheit sowie das breite Spektrum an Geräten und Rechenleistung aufzuklären. Danke für die Perspektive!
Die Sache ist, dass für die Zielgruppe, die diese Perspektive am dringendsten benötigt, alles komplex und alles Zustand ist, weil ihre fn(x) gleich Zustand ist. Der Versuch, den Bulldozer durch die Tür zu quetschen, hat den Laden verwüstet, und jetzt brauchen Sie ihn, um das Chaos aufzuräumen.
Ich stelle nur eine provokante Frage: Glauben Sie, dass die Leserseite von einem WordPress-basierten Blog ein valider Anwendungsfall für React ist? Denn ein Tech-Blog hat kürzlich umgestellt und findet es erstaunlich! Während selbst mit SSR die Ladezeiten schlechter sind als bei einem alten Layout, von Junk befreit...
Ich glaube nicht, dass React – oder irgendein Framework, das sich stark auf JavaScript zur clientseitigen Darstellung von Inhalten konzentriert – für Blogs geeignet ist. Blogs sind ein perfektes Beispiel für das "Dokumenten-Web", bei dem der Hauptzweck darin besteht, gelesene Inhalte abzurufen. Stärke von React ist die Verwaltung komplexer Zustände, und Blogs, wie wir sie typischerweise erleben, haben keine wirkliche Notwendigkeit für ein solches Werkzeug. Wenn jemand darauf für einen Blog besteht, würde ich versuchen, ihn zu überreden, stattdessen Preact zu verwenden.
Wow. Können wir Svelte zum nächsten Artikel hinzufügen?
Toller Artikel, Jeremy. Ich liebe es, wenn Leute sich die Zeit nehmen, Dinge wie diese zu messen und zu vergleichen.
Meine Frage ist, wo würden Sie die Grenze zwischen "einfachem Zustand" und "komplexer Zustandsverwaltung" ziehen? Ich gehe davon aus, dass die Grenze grau und verschwommen sein wird :) Aber selbst Richtlinien für welche Faktoren hilfreich wären. Ich komme von der anderen Seite dieser Diskussion – ich verlasse mich fast ausschließlich auf einfachen Zustand in meiner Arbeit und bin mir nicht sicher, wie komplex ich es werden lassen sollte, bevor ich eine VDOM-Lösung einführe, besonders da ich die Vorteile liebe, die Komponenten bringen. Ich schätze, Leute, die zuerst React gelernt haben, besonders in einer Bootcamp-Umgebung, hätten das gleiche Problem wie ich.
Hey, Julie. Danke für die Frage.
Ich nehme an, die Grenze hängt davon ab, wie viel Zustand Sie bereit sind, selbst zu verwalten. Wie ich im Artikel sagte, funktioniert "einfacher Zustand" am besten für lineare, umschaltbare Dinge. Ich persönlich bin bereit, etwas weiter zu gehen, um Dinge selbst zu verwalten – wie den "Abonnieren"-Button meiner RSS-Feed-App – mit mehr Komplexität. Ja, die Grenze ist sehr unscharf und kann davon abhängen, ob Sie mit einem Team oder alleine arbeiten.
Sie *brauchen* jedoch keine fertige Lösung für die Zustandsverwaltung (aber es hilft). Sie könnten ein Element immer mit Anfangswerten mithilfe von
data-Attributen füllen und dann ein Zustandsobjekt mit diesen Werten füllen und dieses Zustandsobjekt in verschiedenen Event-Handlern verwalten. Das ist im Grunde eine Zustandsmaschine, über die David Khourshid viel spricht. Er leistet viel Arbeit an seinem xstate-Projekt, bietet aber auch eine super minimale Implementierung von xstate an, die unkomprimiert etwa 3 KiB wiegt, genannt @xstate/fsm, die ich gerade für meine eigenen Projekte zu erkunden begonnen habe.Wie auch immer, ich hoffe, das hilft.