In modernen Front-End-Anwendungen ist die CSS-in-JS-Technologie sehr beliebt. Die Sache ist, dass es Entwicklern einen Mechanismus zum Arbeiten mit Stilen gibt, der bequemer ist als normales CSS. Versteh mich nicht falsch. Ich mag CSS sehr, aber eine gute CSS-Architektur zu erstellen ist keine leichte Aufgabe. Die CSS-in-JS-Technologie bietet einige wesentliche
Vorteile gegenüber herkömmlichen CSS-Stilen. Leider kann die Verwendung von CSS-in-JS in bestimmten Anwendungen zu Leistungsproblemen führen. In diesem Artikel werde ich versuchen, die allgemeinen Funktionen der beliebtesten CSS-in-JS-Bibliotheken zu analysieren, einige der Probleme zu erläutern, die manchmal bei ihrer Verwendung auftreten, und Möglichkeiten zur Minderung dieser Probleme vorzuschlagen.

Situationsübersicht
In meiner Firma wurde beschlossen, eine UI-Bibliothek zu erstellen. Dies würde uns erhebliche Vorteile bringen und es uns ermöglichen, Standardfragmente von Schnittstellen in verschiedenen Projekten wiederzuverwenden. Ich war einer der Freiwilligen, die diese Aufgabe übernommen haben. Ich habe mich für die CSS-in-JS-Technologie entschieden, da ich bereits mit der Styling-API der gängigsten CSS-in-JS-Bibliotheken vertraut war. Im Laufe der Arbeit habe ich mich bemüht, vernünftig zu handeln. Ich habe wiederverwendbare Logik entworfen und gemeinsame Eigenschaften in Komponenten angewendet. Deshalb habe ich die Zusammensetzung der Komponenten aufgegriffen. Beispielsweise hat die
<IconButton />
-Komponente die
<IconButton />
-Komponente erweitert, bei der es sich wiederum um eine Implementierung einer einfachen
styled.button
Entität handelte. Leider stellte sich heraus, dass
IconButton
ein eigenes Styling benötigt.
IconButton
habe ich diese Komponente in eine stilisierte Komponente konvertiert:
const IconButton = styled(BaseButton)` border-radius: 3px; `;
Da immer mehr Komponenten in unserer Bibliothek erschienen, verwendeten wir immer mehr Kompositionsoperationen. Das schien nicht unnatürlich. Schließlich ist Komposition die Grundlage von React. Bis zur Erstellung der
Table
Komponente war alles in Ordnung. Ich begann zu spüren, dass diese Komponente langsam gerendert wurde. Besonders in Situationen, in denen die Anzahl der Zeilen in der Tabelle 50 überschritt. Dies war falsch. Daher begann ich das Problem zu verstehen, indem ich auf die Entwicklertools zurückgriff.
Übrigens, wenn Sie sich jemals gefragt haben, warum CSS-Regeln nicht mit dem Developer Tool Inspector bearbeitet werden können, sollten Sie sich darüber im Klaren sein, dass diese
CSSStyleSheet.insertRule () verwenden . Dies ist eine sehr schnelle Möglichkeit, Stylesheets zu ändern. Ein Nachteil ist jedoch, dass die entsprechenden Stylesheets nicht mehr vom Inspector bearbeitet werden können.
Unnötig zu erwähnen, dass der von React erzeugte Baum wirklich riesig war. Die Anzahl der
Context.Consumer
Komponenten war so groß, dass mir der Schlaf
Context.Consumer
werden konnte. Tatsache ist, dass jedes Mal, wenn eine einzelne gestaltete Komponente mithilfe von
gestalteten Komponenten oder
Emotionen gerendert wird, zusätzlich zum Erstellen einer regulären React-Komponente eine zusätzliche
Context.Consumer
Komponente
Context.Consumer
. Dies ist erforderlich, damit das entsprechende Skript (die meisten CSS-in-JS-Bibliotheken hängen von Skripten ab, die ausgeführt werden, während die Seite ausgeführt wird) die generierten Stilregeln korrekt verarbeiten kann. Normalerweise verursacht dies keine besonderen Probleme, aber wir dürfen nicht vergessen, dass die Komponenten Zugriff auf das Thema haben müssen. Dies bedeutet,
Context.Consumer
für jedes stilisierte Element ein zusätzlicher
Context.Consumer
muss, mit dem Sie das Thema aus der
ThemeProvider
Komponente "lesen"
ThemeProvider
. Wenn Sie eine stilisierte Komponente in einer Anwendung mit einem Design erstellen, werden 3 Komponenten erstellt. Dies ist eine
StyledXXX
Komponente und zwei weitere
Context.Consumer
Komponenten.
Hier gibt es zwar nichts besonders Schreckliches, da React seine Arbeit schnell erledigt, was bedeutet, dass wir uns in den meisten Fällen keine Sorgen machen müssen. Was aber, wenn mehrere stilisierte Komponenten zusammengefügt werden, um eine komplexere Komponente zu erstellen? Was ist, wenn diese komplexe Komponente Teil einer langen Liste oder einer großen Tabelle ist, in der mindestens 100 dieser Komponenten gerendert werden? Hier stehen wir in solchen Situationen vor Problemen ...
Profiling
Um verschiedene CSS-in-JS-Lösungen zu testen, habe ich eine einfache Anwendung erstellt. Es zeigt den
Hello World
Text 50 Mal an. In der
ersten Version dieser Anwendung habe ich diesen Text in ein reguläres
div
Element eingeschlossen. In der
zweiten habe ich die
styled.div
Komponente verwendet. Außerdem habe ich der Anwendung eine Schaltfläche hinzugefügt, mit der alle 50 Elemente neu gerendert werden.
Nach dem Rendern der
<App />
-Komponente wurden zwei verschiedene Reaktionsbäume angezeigt. Die folgenden Abbildungen zeigen die von React abgeleiteten Elementbäume.
Ein Baum, der in einer Anwendung angezeigt wird, die ein reguläres div-Element verwendetEin Baum, der in einer Anwendung angezeigt wird, die styled.div verwendetAnschließend habe ich mit der Schaltfläche 10-mal
<App />
gerendert, um Daten zur Systemlast zu erfassen, die zusätzliche Komponenten von
Context.Consumer
. Hier finden Sie Informationen zum wiederholten Rendern einer Anwendung mit regulären
div
Elementen im Entwurfsmodus.
Erneutes Rendern der Anwendung mit regulären div-Elementen im Entwurfsmodus. Der Mittelwert beträgt 2,54 ms.Erneutes Rendern der Anwendung mit styled.div-Elementen im Entwicklungsmodus. Der Durchschnittswert beträgt 3,98 ms.Sehr interessant ist, dass eine CSS-in-JS-Anwendung im Durchschnitt 56,6% langsamer ist als üblich. Aber es war ein Entwicklungsmodus. Was ist mit dem Produktionsmodus?
Erneutes Rendern der Anwendung mit den üblichen div-Elementen im Produktionsmodus. Der Durchschnittswert beträgt 1,06 ms.Erneutes Rendern der Anwendung mit styled.div-Elementen im Produktionsmodus. Der Durchschnittswert beträgt 2,27 ms.Wenn der Produktionsmodus aktiviert ist, scheint die div-Implementierung der Anwendung im Vergleich zur gleichen Version im Entwicklungsmodus mehr als 50% schneller zu sein. Eine styled.div-Anwendung ist nur 43% schneller. Und hier ist nach wie vor klar, dass die CSS-in-JS-Lösung fast doppelt so langsam ist wie die übliche Lösung. Was verlangsamt ihn?
Analyse der Anwendung während ihrer Ausführung
Die offensichtliche Antwort auf die Frage, was eine CSS-in-JS-Anwendung verlangsamt,
Context.Consumer
folgt: „Es wurde gesagt, dass hundert CSS-in-JS-Bibliotheken zwei
Context.Consumer
pro Komponente rendern.“ Wenn Sie dies alles
Context.Consumer
ist
Context.Consumer
nur ein Mechanismus für den Zugriff auf eine JS-Variable. Natürlich muss React einige Arbeiten ausführen, um herauszufinden, wo der entsprechende Wert abgelesen werden soll. Dies allein erklärt jedoch nicht die obigen Messergebnisse. Die eigentliche Antwort auf diese Frage können Sie finden, indem Sie den Grund für die Verwendung von
Context.Consumer
. Tatsache ist, dass die meisten CSS-in-JS-Bibliotheken auf Skripts angewiesen sind, die während der Seitenausgabe im Browser ausgeführt werden und die Bibliotheken dabei unterstützen, Komponentenstile dynamisch zu aktualisieren. Diese Bibliotheken erstellen während der Seitenmontage keine CSS-Klassen. Stattdessen generieren und aktualisieren sie dynamisch
<style>
-Tags im Dokument. Dies geschieht, wenn die Komponenten montiert werden oder wenn sich ihre Eigenschaften ändern. Diese Tags enthalten normalerweise eine einzelne CSS-Klasse, deren Hash-Name einer einzelnen React-Komponente zugeordnet ist. Wenn sich die Eigenschaften dieser Komponente ändern, muss sich auch das entsprechende
<style>
-Tag ändern. So beschreiben Sie, was während dieses Vorgangs geschieht:
- Die CSS-Regeln, die das
<style>
-Tag haben muss, werden neu generiert. - Es wird ein neuer Hash-Klassenname erstellt, der zum Speichern der oben genannten CSS-Regeln verwendet wird.
- Die
classname
der entsprechenden React-Komponente wird auf eine neue aktualisiert, die die gerade erstellte Klasse angibt.
Betrachten Sie beispielsweise die Bibliothek mit
styled-components
. Beim Erstellen der
styled.div
Komponente
styled.div
Bibliothek dieser Komponente
einen internen Bezeichner (ID) zu und fügt dem HTML-Tag
<head>
ein neues
<style>
-Tag hinzu. Dieses Tag enthält einen einzelnen Kommentar, der auf die interne Kennung der React-Komponente verweist, zu der der entsprechende Stil gehört:
<style data-styled-components> </style>
Und hier sind die Aktionen, die die Bibliothek für gestaltete Komponenten ausführt, wenn die entsprechende React-Komponente angezeigt wird:
- Analysiert CSS-Regeln aus der Vorlagenzeichenfolge für gestaltete Komponenten.
- Generiert einen neuen CSS-Klassennamen (oder ermittelt, ob der vorherige Name beibehalten werden soll).
- Führt die Vorverarbeitung von Stilen mithilfe von Stiften durch.
- Bettet das aus der Vorverarbeitung resultierende CSS in das entsprechende
<style>
-Tag des HTML- <head>
.
Um das Thema in Schritt 1 dieses Prozesses verwenden zu können, ist
Context.Consumer erforderlich . Dank dieser Komponente werden die Werte aus dem Thema in der Vorlagenzeichenfolge gelesen. Um das mit dieser Komponente
Context.Consumer
<style>
-Tag ändern zu können, ist ein weiterer
Context.Consumer
aus der Komponente erforderlich. Hiermit können Sie auf eine Instanz eines Stylesheets zugreifen. Aus diesem Grund stoßen wir in den meisten CSS-in-JS-Bibliotheken auf zwei Instanzen von
Context.Consumer
.
Da sich alle diese Berechnungen auf die Benutzeroberfläche auswirken, ist außerdem zu beachten, dass sie während der Rendering-Phase der Komponenten ausgeführt werden müssen. Sie können nicht im Code von Ereignishandlern für den Lebenszyklus von React-Komponenten ausgeführt werden (auf diese Weise kann ihre Ausführung verzögert werden und sieht für den Benutzer nach langsamer Seitenbildung aus). Deshalb ist das Rendern von styled.div-Anwendungen langsamer als das Rendern einer normalen Anwendung.
All dies wurde von Design-Komponenten-Entwicklern bemerkt. Sie haben die Bibliothek optimiert, um die Zeit für das erneute Rendern von Komponenten zu reduzieren. Insbesondere stellt die Bibliothek fest, ob die stilisierte Komponente „statisch“ ist. Das heißt, ob die Stile der Komponente vom Thema oder von den Eigenschaften abhängen, die an sie übergeben werden. Die statische Komponente ist beispielsweise wie folgt dargestellt:
const StaticStyledDiv = styled.div` color:red `;
Und diese Komponente ist nicht statisch:
const DynamicStyledDiv = styled.div` color: ${props => props.color} `;
Wenn die Bibliothek feststellt, dass die Komponente statisch ist, werden die obigen
4 Schritte übersprungen, um zu erkennen, dass der generierte Klassenname niemals geändert werden muss (da kein dynamisches Element vorhanden ist, für das möglicherweise CSS-Regeln geändert werden müssen). In dieser Situation zeigt die Bibliothek außerdem
nicht ThemeContext.Consumer
die stilisierte Komponente an, da die
ThemeContext.Consumer
nicht mehr zulässt, dass die Komponente als "statisch" betrachtet wird.
Wenn Sie bei der Analyse zuvor dargestellter Screenshots vorsichtig genug waren, können Sie feststellen, dass auch im Produktionsmodus für jedes
styled.div
zwei
Context.Consumer
Komponenten
Context.Consumer
. Interessanterweise war die gerenderte Komponente "statisch", da mit ihr keine dynamischen CSS-Regeln verknüpft waren. In einer solchen Situation würde man erwarten, dass dieses Beispiel, wenn es mit der Bibliothek für gestaltete Komponenten geschrieben wurde, nicht
Context.Consumer
für die Arbeit mit dem Thema erforderlich ist. Der Grund, warum genau zwei
Context.Consumer
hier angezeigt werden, ist, dass das Experiment, dessen Daten oben angegeben sind, mit emotion durchgeführt wurde - einer weiteren CSS-in-JS-Bibliothek. Diese Bibliothek verwendet fast den gleichen Ansatz wie gestaltete Komponenten. Die Unterschiede zwischen ihnen sind gering. Daher analysiert die Emotionsbibliothek die Vorlagenzeichenfolge, verarbeitet die Stile mithilfe von
Stilen vor und aktualisiert den Inhalt des entsprechenden
<style>
. Hierbei ist jedoch ein wesentlicher Unterschied zwischen Stilelementen und Emotionen zu beachten. Es besteht darin, dass die Emotionsbibliothek
immer alle Komponenten in
ThemeContext.Consumer
- unabhängig davon, ob sie das Thema verwenden oder nicht (dies erklärt das Erscheinungsbild des obigen Screenshots). Interessanterweise
übertreffen Emotionen gestaltete Komponenten in Bezug auf die Leistung, obwohl Emotionen mehr Verbraucherkomponenten als gestaltete Komponenten darstellen. Dies weist darauf hin, dass die Anzahl der
Context.Consumer
Komponenten kein wesentlicher Faktor für die Verlangsamung des Renderns ist. Es ist anzumerken, dass zum Zeitpunkt des Schreibens dieses Materials eine Beta-Version von styled-components v5.xx veröffentlicht wurde, die laut den Entwicklern der Bibliothek
die Emotionen in Bezug auf die Leistung
umgeht .
Fassen Sie zusammen, worüber wir gesprochen haben. Es stellt sich heraus, dass eine Kombination vieler
Context.Consumer
Elemente (was bedeutet, dass React die Arbeit zusätzlicher Elemente koordinieren muss) und interne dynamische Styling-Mechanismen die Anwendung verlangsamen können. Ich muss sagen, dass alle
<style>
-Tags, die der
<head>
für jede Komponente hinzugefügt wurden, niemals gelöscht werden. Dies ist auf die Tatsache zurückzuführen, dass Elemententfernungsvorgänge das DOM stark belasten (z. B. muss der Browser die Seite aus diesem Grund neu anordnen). Diese Belastung ist höher als die zusätzliche Belastung des Systems, die durch das Vorhandensein unnötiger
<style>
-Elemente auf der Seite verursacht wird. Um ehrlich zu sein, kann ich nicht mit Sicherheit sagen, dass unnötige
<style>
-Tags zu Leistungsproblemen führen können. Sie speichern einfach nicht verwendete Klassen, die während des Betriebs der Seite generiert wurden (dh diese Daten wurden nicht über das Netzwerk übertragen). Sie sollten jedoch über diese Funktion der Verwendung der CSS-in-JS-Technologie Bescheid wissen.
Ich muss sagen, dass die
<style>
-Tags nicht alle CSS-in-JS-Bibliotheken erstellen, da nicht alle auf den Mechanismen basieren, die funktionieren, wenn Seiten in Browsern funktionieren. Beispielsweise führt die
Linaria- Bibliothek überhaupt nichts aus, während die Seite im Browser ausgeführt wird.
Es definiert eine Reihe fester CSS-Klassen während des Erstellungsprozesses des Projekts und passt die Entsprechung aller dynamischen Regeln in der Vorlagenzeichenfolge (dh CSS-Regeln, die von den Eigenschaften der Komponente abhängen) mit benutzerdefinierten CSS-Eigenschaften an. Wenn sich eine Komponenteneigenschaft ändert, ändert sich daher die CSS-Eigenschaft und das Aussehen der Benutzeroberfläche. Dank dessen ist Linaria viel schneller als Bibliotheken, die auf Mechanismen angewiesen sind, die funktionieren, während Seiten ausgeführt werden. Die Sache ist, dass bei Verwendung dieser Bibliothek das System beim Rendern von Komponenten viel weniger Berechnungen durchführen muss. Wenn Sie beim Rendern Linaria verwenden, müssen Sie nur daran denken,
die benutzerdefinierte CSS-Eigenschaft zu
aktualisieren . Gleichzeitig ist dieser Ansatz jedoch nicht mit IE11 kompatibel, bietet nur eingeschränkte Unterstützung für gängige CSS-Eigenschaften und erlaubt Ihnen ohne zusätzliche Konfiguration nicht, Designs zu verwenden. Wie in anderen Bereichen der Webentwicklung gibt es auch in CSS-in-JS-Bibliotheken keine ideale, für alle Gelegenheiten geeignete.
Zusammenfassung
Die CSS-in-JS-Technologie sah früher wie eine Revolution im Bereich des Stylings aus. Es hat vielen Entwicklern das Leben erleichtert und es ermöglicht, ohne zusätzlichen Aufwand viele Probleme zu lösen, wie z. B. Namenskollisionen und die Verwendung von Präfixen der Browserhersteller. Dieser Artikel beleuchtet die Frage, wie beliebte CSS-in-JS-Bibliotheken (die Stile steuern, während eine Seite ausgeführt wird) die Leistung von Webprojekten beeinflussen können. Ich möchte besonders darauf hinweisen, dass der Einfluss dieser Bibliotheken auf die Performance nicht immer zu spürbaren Problemen führt. Tatsächlich ist dieser Effekt in den meisten Anwendungen völlig unsichtbar. In Anwendungen mit Seiten, die Hunderte komplexer Komponenten enthalten, können Probleme auftreten.
Die Vorteile von CSS-in-JS überwiegen normalerweise die potenziellen negativen Auswirkungen der Verwendung dieser Technologie. Die Nachteile von CSS-in-JS sollten jedoch von den Entwicklern in Betracht gezogen werden, deren Anwendungen große Datenmengen wiedergeben, deren Projekte viele sich ständig ändernde Oberflächenelemente enthalten. Wenn Sie den Verdacht haben, dass Ihre Anwendung den negativen Auswirkungen von CSS-in-JS ausgesetzt ist, lohnt es sich, vor dem Umgestalten alles richtig zu bewerten und zu messen.
Im Folgenden finden Sie einige Tipps zur Verbesserung der Anwendungsleistung, die die beliebten CSS-in-JS-Bibliotheken verwenden, die ihre Aufgabe erfüllen, wenn Seiten in einem Browser ausgeführt werden:
- Lassen Sie sich nicht zu sehr von der Zusammensetzung der stilisierten Komponenten mitreißen. Versuchen Sie nicht, den Fehler zu wiederholen, von dem ich zu Beginn gesprochen habe, und versuchen Sie nicht, eine Komposition aus drei stilisierten Komponenten zu erstellen, um eine unglückliche Schaltfläche zu erstellen. Wenn Sie den Code "wiederverwenden" möchten, verwenden Sie die CSS-Eigenschaft und erstellen Sie Vorlagenzeichenfolgen. Auf diese Weise können Sie auf die vielen unnötigen Komponenten von
Context.Consumer
. Infolgedessen muss React weniger Komponenten verwalten, was die Projektproduktivität erhöht. - Bemühen Sie sich, "statische" Komponenten zu verwenden. Einige CSS-in-JS-Bibliotheken optimieren den generierten Code, wenn die Stile der Komponente nicht vom Thema oder von den Eigenschaften abhängen. Je „statischer“ die Vorlagenzeichenfolgen sind, desto höher ist die Wahrscheinlichkeit, dass Skripts in CSS-in-JS-Bibliotheken schneller ausgeführt werden.
- Vermeiden Sie unnötige Neuerstellungen Ihrer React-Anwendungen. Bemühen Sie sich, nur zu rendern, wenn Sie es wirklich brauchen. Dank dessen werden weder React-Aktionen noch CSS-in-JS-Bibliotheksaktionen geladen. Das erneute Rendern ist eine Operation, die nur in Ausnahmefällen durchgeführt werden sollte. Zum Beispiel - bei gleichzeitiger Entnahme einer großen Anzahl "schwerer" Bauteile.
- Finden Sie heraus, ob eine CSS-in-JS-Bibliothek für Ihr Projekt geeignet ist, die keine Skripts verwendet, die ausgeführt werden, während die Seite im Browser ausgeführt wird. Manchmal entscheiden wir uns für die CSS-in-JS-Technologie, weil es für den Entwickler bequemer ist, sie zu verwenden, und nicht für verschiedene JavaScript-APIs. Wenn Ihre Anwendung keine Unterstützung benötigt, wenn sie die CSS-Eigenschaften nicht intensiv nutzt, können Sie möglicherweise eine CSS-in-JS-Bibliothek wie Linaria verwenden, die keine Skripts verwendet, die ausgeführt werden, während die Seite ausgeführt wird. Durch diesen Ansatz wird außerdem die Größe des Anwendungsbündels um etwa 12 KB verringert. Tatsache ist, dass die Codegröße der meisten CSS-in-JS-Bibliotheken in 12-15 KB passt und der Code derselben Linaria weniger als 1 KB beträgt.
Sehr geehrte Leser! Verwenden Sie CSS-in-JS-Bibliotheken?
