DRY-ing up styled-components

Avatar of Allie Stehney
Allie Stehney on

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Ich arbeite gerne mit styled-components. Sie ermöglichen es Ihnen, CSS in Ihrem JavaScript zu schreiben, wodurch Ihr CSS in sehr geringer Entfernung zu Ihrem JavaScript für eine einzelne Komponente gehalten wird. Als Front-End-Entwickler, der es liebt, eine Webseite zu sezieren und in wiederverwendbare Komponenten zu zerlegen, bereitet mir die Idee von styled-components Freude. Der Ansatz ist sauber und modular, und ich muss nicht in einer gigantischen CSS-Datei nachschlagen, ob eine benötigte Klasse bereits existiert. Ich muss auch keine Klasse zu dieser endlosen CSS-Datei *hinzufügen* und mich schuldig fühlen, diese ohnehin schon gigantische Datei noch größer zu machen.

Allerdings habe ich auf meinem Weg zur Nutzung von styled-components begonnen zu erkennen, dass es zwar großartig ist, CSS-Stile für einzelne Komponenten spezifisch zu trennen, ich mich aber oft *wiederholen* muss, um meine Stile pro Komponente zu organisieren. Ich habe neue CSS-Deklarationen und damit neue styled-components für jede Komponente erstellt und sehe eine große Menge an Duplikaten in meinem CSS. Nein, die styled-components sind nicht immer exakt gleich, aber zwei von drei CSS-Zeilen würden mit zwei von drei CSS-Zeilen in einer anderen Komponente übereinstimmen. Muss ich diesen Code wirklich jedes Mal wiederholen, wenn ich diese Stile brauche?

Nehmen wir zum Beispiel Flexbox. Flexbox ist großartig für responsive Layouts. Es richtet Elemente auf eine bestimmte Weise aus und kann mit minimalen Änderungen angepasst werden, um auf verschiedenen Bildschirmgrößen gut auszusehen. Daher schreibe ich meistens

display: flex;
flex-direction: row; /* the default; in react native, column is the default */

Fast genauso oft fand ich mich beim Schreiben von

display: flex;
flex-direction: column;

Die beiden obigen Codeausschnitte sind ziemlich gebräuchlich: Der erste nimmt alle Kindelemente und positioniert sie nebeneinander – von links nach rechts – in einer Zeile. Der zweite nimmt alle Kindelemente und positioniert sie übereinander – von oben nach unten – in einer Spalte. Jeder dieser Codeausschnitte kann spezifischer gemacht werden; wir können jedoch verschiedene Eigenschaften hinzufügen, um genauer zu bestimmen, wie die Kindelemente auf der Seite angeordnet werden sollen. Wenn wir beispielsweise möchten, dass die Elemente gleichmäßig über die verfügbare Bildschirmbreite verteilt sind, können wir jedem Codeausschnitt die folgende Zeile hinzufügen

justify-content: space-evenly;

Darüber hinaus gibt es andere Eigenschaften wie align-items, die wir hinzufügen können, um das Layout dieser Elemente weiter anzupassen. Wenn wir also drei verschiedene Komponenten haben, die alle ein Flexbox-Layout benötigen, aber zusätzliche unterschiedliche Eigenschaften aufweisen, wie können wir styled-components auf eine nicht repetitive Weise verwenden? 

Anfänglich ist es sinnvoll, drei Stilsätze für jede Komponente zu erstellen, wie hier

// component one
const ComponentOne = styled.div`
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
`


// component two
const ComponentTwo = styled.div`
  display: flex;
  flex-direction: row;
  justify-content: space-between;
`


// component three
const ComponentThree = styled.div`
  display: flex;
  flex-direction: row;
  justify-content: space-evenly;
`

Die oben aufgeführten Stile werden ihre Aufgabe erfüllen. Alle drei Komponenten haben Kindelemente, die in einer Zeile angeordnet sind – von links nach rechts positioniert – mit unterschiedlichen Abständen zwischen jedem Kindelement. Das Having derselben zwei Codezeilen dreimal wiederholt, führt jedoch zu CSS-Bloat.

Um Wiederholungen zu vermeiden, können wir eine Basiskomponente erweitern auf jede der anderen Komponenten und dann die zusätzlichen Stile hinzufügen, die wir für diese Komponenten benötigen.

// flex row component
const ExampleFlex = `
  display: flex;
  flex-direction: row;
`


// component one
const ComponentOne = styled(ExampleFlex)`
  justify-content: flex-start;
`


// component two
const ComponentTwo = styled(ExampleFlex)`
  justify-content: space-between;
`


// component three
const ComponentThree = styled(ExampleFlex)`
  justify-content: space-evenly;
`

Das fühlt sich schon viel besser an. Diese Version – bei der wir von der Komponente <ExampleFlex /> erweitern – entfernt nicht nur repetitiven Code, sondern hält auch den Code, der sich auf die Anzeige von Elementen in einer Zeile bezieht, an einem Ort. Wenn wir diesen Code, der sich auf die Ausrichtung der Elemente bezieht, in eine Spalte ändern müssten, können wir das an einem Ort statt an dreien tun.

Wichtiger Hinweis: Wenn Sie Stile von einer anderen Komponente erben, müssen die Stile, die von der Basiskomponente erben, nach der Basiskomponente aufgeführt werden, wie im obigen Beispiel. Wenn Sie <ComponentOne /> über <ExampleFlex /> platzieren, würde dies zu einem Fehler führen: Uncaught ReferenceError: Cannot access ‘ExampleFlex’ before initialization.

Um diese Idee noch weiter zu treiben, zeigt die folgende Demo eine Situation, in der Sie ähnliche Stile auf zwei verschiedenen UI-Elementen auf der Seite benötigen könnten, diese Elemente aber jeweils ihre leichten Unterschiede haben.

Wie Sie sehen können, müssen sowohl die Navigation oben auf der Seite als auch der Footer unten auf der Seite auf größeren Bildschirmen in Zeilenrichtung angeordnet und dann auf mobilen Geräten in ein Spaltenlayout verschoben werden. Die beiden Elemente unterscheiden sich jedoch darin, dass die Navigation oben auf der Seite nach links ausgerichtet sein muss, um Platz für das Logo rechts zu lassen, während die Footer-Links nach rechts ausgerichtet sein müssen. Aufgrund dieser Unterschiede ist es sinnvoll, für jedes dieser Elemente zwei verschiedene gestylte Komponenten zu erstellen; das Element <Navigation /> für die obere Navigation und die Komponente <Footer /> für die Navigation am unteren Rand der Seite. 

Die Stile der oberen Navigation sehen so aus

const Navigation = styled.div`
  align-items: center;
  display: flex;
  flex-direction: row;
  justify-content: space-between;
  padding: 10px;
  width: calc(100% - 20px);


  @media (max-width: 768px) {
    flex-direction: column;
  }
`

Während die Stile des unteren Footers so aussehen

const Footer = styled.div`
  align-items: center;
  display: flex;
  flex-direction: row; 
  justify-content: flex-end;
  padding: 10px;
  width: calc(100% - 20px);


  @media (max-width: 768px) {
    flex-direction: column;
  }
`

Der einzige Unterschied bei diesen beiden Elementen? Die Eigenschaft justify-content. Zusätzlich verwendet die Komponente <LeftSideNav /> display: flex mit derselben Media Query. 

Abgesehen davon, dass diese drei Komponenten viel vom gleichen CSS gemeinsam haben, sind die Komponenten <NavItem /> und <FooterNavItem /> sehr ähnliche Link-Komponenten mit geringfügigen Unterschieden. Wie können wir das DRY machen?

Wenn Sie sich das folgende Beispiel ansehen, werden Sie sehen, dass wiederverwendbares CSS über mehrere Komponenten hinweg in eine eigene Komponente extrahiert werden kann, von der wir erweitern, um die spezifischen Änderungen vorzunehmen, die wir für spezifischere Komponenten benötigen. 

Mit den vorgenommenen Änderungen ist unsere JavaScript-Datei, die alle styled-components enthält, 10 Zeilen kürzer als die vorherige Version. Auch wenn das geringfügig erscheinen mag, könnten diese Änderungen bei wachsenden Anwendungen dazu beitragen, das mit einer Anwendung ausgelieferte CSS zu minimieren, wenn Stile weiter hinzugefügt werden.

Es gibt auch die polymorphe "as"-Prop!

Zusätzlich zur Erweiterung von Stilen von einer Komponente in eine andere bietet uns styled-components eine polymorphe Prop namens "as", die Stile einer gegebenen Komponente auf ein anderes Element anwendet, solange dieses neue Element spezifiziert ist. Dies ist hilfreich in Situationen, in denen die Benutzeroberfläche für zwei Elemente gleich aussehen mag, die zugrunde liegende HTML-Funktionalität dieser beiden Elemente jedoch unterschiedlich ist.

Nehmen wir Buttons und Links, die als Buttons gestylt sind, als Beispiel. Während sie auf den ersten Blick ähnliche Funktionalität zu haben scheinen – sie können beide angeklickt werden, um eine Aktion auszulösen – erfüllen die beiden funktionell unterschiedliche Zwecke. Ein Button eignet sich hervorragend zum Absenden eines Formulars oder zum Ändern des Layouts der aktuellen Seite, aber ein Link führt Sie zu einer Ressource.

Ich habe unten einen Pen erstellt, der dies veranschaulicht. Auf den ersten Blick sehen die beiden Buttons gleich aus. Der rechte ist jedoch tatsächlich ein <a>-Element, das wie ein Button gestylt ist, während der linke ein tatsächliches <button>-Element ist. Diese beiden Buttons könnten später Teil einer Website-Navigation sein, wobei einer zu einer externen Website verlinken muss. Bei einem ersten Versuch, diese beiden Elemente zu erstellen, ist es sinnvoll, Code für jede Komponente zu sehen, wie im ersten Beispiel, das wir uns angesehen haben.

Wenn wir wissen, dass diese beiden Elemente exakt dieselben Stile haben werden, können wir jeden als <Button /> stylen und die gestylte Komponente mit dem zugehörigen JavaScript für den Button gruppieren, den wir erstellen. Dann wenden wir exakt dieselben Stile auf eine <Link />-Komponente an, die wir spezifizieren.

Wenn Sie sich den obigen Pen ansehen, sehen Sie, dass wir jeglichen doppelten CSS entfernt haben, der sich auf das Styling dieser beiden Elemente bezieht. Anstatt das CSS für den Button für die Link-Komponente zu wiederholen, können wir einen Wert an die "as"-Prop übergeben, wie folgt:

<Button as="a" href="#" ... >I am a Link!</Button>

Dies gibt uns die Möglichkeit, das Element in ein HTML-Tag zu ändern und dabei die bereits für den Button definierten Stile beizubehalten.


Das Erweitern von Basisstilen – und vielleicht sogar das Aufbewahren einiger davon in einer einzigen globalStyles.js-Datei – ist eine effektive Methode, um unseren styled-components-Code DRY zu gestalten und ihn wesentlich besser handhabbar zu machen. CSS auf ein Minimum zu beschränken, verbessert nicht nur die Leistung einer Website, sondern kann Entwickler auch davor bewahren, in Zukunft zeilenweise CSS durchsuchen zu müssen.