In einer perfekten Welt hätten unsere Projekte unbegrenzte Ressourcen und Zeit. Unsere Teams würden mit durchdachten und hoch verfeinerten UX-Designs mit dem Coden beginnen. Es gäbe einen Konsens unter den Entwicklern über die beste Vorgehensweise beim Styling. Es gäbe einen oder mehrere CSS-Gurus im Team, die sicherstellen könnten, dass Funktionalität und Stil gleichzeitig ausgerollt werden, ohne dass es zu einem Desaster wird.
Ich habe das tatsächlich schon in großen Unternehmensumgebungen erlebt. Es ist eine schöne Sache. Dieser Artikel ist nicht für diese Leute.
Auf der anderen Seite des Spektrums steht das winzige Startup, das keine Finanzierung hat, ein oder zwei Frontend-Entwickler und einen sehr kurzen Zeitplan, um einige Funktionalitäten zu demonstrieren. Es muss nicht perfekt aussehen, aber es sollte zumindest auf Desktops, Tablets und Mobilgeräten vernünftig gerendert werden. Dies bringt sie zu einem Punkt, an dem es Beratern und frühen Nutzern gezeigt werden kann; vielleicht sogar potenziellen Investoren, die Interesse an dem Konzept bekundet haben. Sobald sie etwas Cashflow aus Verkäufen und/oder Investitionen erhalten, können sie einen dedizierten UX-Designer engagieren und die Benutzeroberfläche polieren.
Das Folgende ist für diese letztere Gruppe.
Projekt-Kickoff-Meeting
Erfinden wir ein Unternehmen, um ins Rollen zu kommen.
Solar Excursions ist ein kleines Reisebüro, das sich zum Ziel gesetzt hat, die aufstrebende Raumtourismusbranche der nahen Zukunft zu bedienen.
Unser kleines Entwicklungsteam hat vereinbart, dass React für die UI verwendet wird. Einer unserer Frontend-Entwickler ist ein großer Fan von Sass und der andere ist vom CSS in JavaScript begeistert. Aber sie werden unter Druck stehen, ihre anfänglichen Sprintziele zu erreichen; es gibt sicherlich keine Zeit, über den bestmöglichen Styling-Ansatz zu streiten. Beide Coder sind sich einig, dass die Wahl auf lange Sicht nicht viel ausmacht, solange sie konsequent ausgeführt wird. Sie sind sicher, dass die Implementierung des Stylings von Grund auf unter Zeitdruck technische Schulden aufbaut, die später bereinigt werden müssen.
Nach einiger Diskussion entscheidet sich das Team, eine oder mehrere "Styling-Refactoring"-Sprints zu planen. Vorerst konzentrieren wir uns darauf, etwas auf den Bildschirm zu bringen, indem wir React-Bootstrap verwenden. Auf diese Weise können wir schnell funktionierende Desktop- und Mobil-Layouts ohne viel Aufwand erstellen.
Je weniger Zeit für Frontend-Styling aufgewendet wird, desto besser, denn wir werden auch die UI an die Dienste anbinden müssen, die unser Backend-Entwickler ausarbeiten wird. Und da sich unsere Anwendungsarchitektur zu formen beginnt, sind sich beide Frontend-Entwickler einig, dass es wichtig ist, dass sie Unit-Tests durchführt. Sie haben viel auf dem Teller.
Basierend auf meinen Diskussionen mit den Entscheidungsträgern habe ich mich als engagierter Projektmanager mindestens zehn Minuten lang mit Balsamiq abgemüht, um dem Team Mockups für die Buchungsseite auf Desktop und Mobilgeräten zur Verfügung zu stellen. Ich gehe davon aus, dass sie Tablets dazwischen einbinden und vernünftig aussehen werden.

Sprint Zero: Review Meeting
Pizza für alle! Das Team hat wirklich hart gearbeitet, um seine Ziele zu erreichen, und wir haben jetzt eine Buchungsseite mit einem Layout, das die Mockups annähert. Die Infrastruktur für die Dienste kommt zustande, aber es ist noch ein langer Weg, bevor wir die UI damit verbinden können. In der Zwischenzeit verwenden die Frontend-Entwickler eine hartkodierte Mock-Datenstruktur.

Hier ist ein Blick auf unseren UI-Code bisher
Das ist alles geradliniges React. Wir verwenden einige dieser Hooks-Heißheiten, aber es ist wahrscheinlich für die meisten von Ihnen bereits passé.
Der wichtigste Punkt, den man hier bemerken sollte, ist, wie vier unserer fünf Anwendungskomponenten Komponenten von react-bootstrap importieren und verwenden. Nur die Haupt-App-Komponente ist unbeeinflusst. Das liegt daran, dass sie nur die oberste Ansicht mit unseren benutzerdefinierten Komponenten zusammensetzt.
// App.js imports
import React, { useState } from "react";
import Navigation from "./Navigation";
import Page from "./Page";
// Navigation.js imports
import React from "react";
import { Navbar, Dropdown, Nav } from "react-bootstrap";
// Page.js imports
import React from "react";
import PosterCarousel from "./PosterCarousel";
import DestinationLayout from "./DestinationLayout";
import { Container, Row, Col } from "react-bootstrap";
// PosterCarousel.js imports
import React from "react";
import { Alert, Carousel, Image } from "react-bootstrap";
// DestinationLayout.js imports
import React, { useState, useEffect } from "react";
import {
Button,
Card,
Col,
Container,
Dropdown,
Jumbotron,
ListGroup,
Row,
ToggleButtonGroup,
ToggleButton
} from "react-bootstrap";
Die Entscheidung, mit Bootstrap schnell voranzugehen, hat uns erlaubt, unsere Sprintziele zu erreichen, aber wir sammeln bereits technische Schulden an. Das sind nur vier betroffene Komponenten, aber da die Anwendung wächst, ist es klar, dass die "Styling-Refactoring"-Sprints, die wir geplant haben, exponentiell schwieriger werden. Und wir haben die Komponenten noch nicht einmal viel angepasst. Sobald wir Dutzende von Komponenten haben, die alle Bootstrap mit viel Inline-Styling verwenden, um sie aufzuhübschen, wird das Refactoring, um React-Bootstrap-Abhängigkeiten zu entfernen, eine wirklich beängstigende Aufgabe sein.
Anstatt weitere Seiten der Buchungs-Pipeline zu erstellen, entscheidet sich das Team, den nächsten Sprint damit zu verbringen, die Verwendung von React-Bootstrap in einem benutzerdefinierten Komponentensatz zu isolieren, da unsere Dienste noch im Aufbau sind. Anwendungskomponenten verwenden nur Komponenten aus diesem Kit. Auf diese Weise wird der Prozess, wenn es darum geht, uns von React-Bootstrap zu entwöhnen, viel einfacher sein. Wir müssen nicht dreißig Verwendungen der React-Bootstrap-Button-Komponente in der gesamten App refaktorieren, sondern schreiben nur die internen Elemente unserer KitButton-Komponente neu.
Sprint One: Review Meeting
Nun, das war einfach. High-Fives. Keine Änderung am visuellen Erscheinungsbild der UI, aber wir haben jetzt einen "Kit"-Ordner, der neben "Components" in unserer React-Quelle liegt. Er enthält eine Reihe von Dateien wie KitButton.js, die im Grunde umbenannte React-Bootstrap-Komponenten exportieren.
Ein Beispiel für eine Komponente aus unserem Kit sieht so aus
// KitButton.js
import { Button, ToggleButton, ToggleButtonGroup } from "react-bootstrap";
export const KitButton = Button;
export const KitToggleButton = ToggleButton;
export const KitToggleButtonGroup = ToggleButtonGroup;
Wir bündeln all diese Kit-Komponenten in einem Modul wie diesem
// kit/index.js
import { KitCard } from "./KitCard";
import { KitHero } from "./KitHero";
import { KitList } from "./KitList";
import { KitImage } from "./KitImage";
import { KitCarousel } from "./KitCarousel";
import { KitDropdown } from "./KitDropdown";
import { KitAttribution } from "./KitAttribution";
import { KitNavbar, KitNav } from "./KitNavbar";
import { KitContainer, KitRow, KitCol } from "./KitContainer";
import { KitButton, KitToggleButton, KitToggleButtonGroup } from "./KitButton";
export {
KitCard,
KitHero,
KitList,
KitImage,
KitCarousel,
KitDropdown,
KitAttribution,
KitButton,
KitToggleButton,
KitToggleButtonGroup,
KitContainer,
KitRow,
KitCol,
KitNavbar,
KitNav
};
Und jetzt sind unsere Anwendungskomponenten vollständig von React-Bootstrap befreit. Hier sind die Importe für die betroffenen Komponenten
// Navigation.js imports
import React from "react";
import { KitNavbar, KitNav, KitDropdown } from "../kit";
// Page.js imports
import React from "react";
import PosterCarousel from "./PosterCarousel";
import DestinationLayout from "./DestinationLayout";
import { KitContainer, KitRow, KitCol } from "../kit";
// PosterCarousel.js imports
import React from "react";
import { KitAttribution, KitImage, KitCarousel } from "../kit";
// DestinationLayout.js imports
import React, { useState, useEffect } from "react";
import {
KitCard,
KitHero,
KitList,
KitButton,
KitToggleButton,
KitToggleButtonGroup,
KitDropdown,
KitContainer,
KitRow,
KitCol
} from "../kit";
Hier ist der Frontend-Codebestand jetzt
Obwohl wir alle React-Imports in unseren Kit-Komponenten gesammelt haben, sind unsere Anwendungskomponenten immer noch ein wenig auf die React-Bootstrap-Implementierung angewiesen, da die Attribute, die wir auf unseren Kit-Komponenten-Instanzen platzieren, dieselben sind wie die von React-Bootstrap. Das schränkt uns bei der Neuimplementierung der Kit-Komponenten ein, da wir uns an die gleiche API halten müssen. Zum Beispiel
// From Navigation.js
<KitNavbar bg="dark" variant="dark" fixed="top">
Idealerweise müssten wir diese React-Bootstrap-spezifischen Attribute nicht hinzufügen, wenn wir unsere KitNavbar instanziieren.
Die Frontend-Entwickler versprechen, diese im Laufe der Zeit auszufacatorieren, jetzt, da wir sie als problematisch identifiziert haben. Und alle neuen Referenzen auf React-Bootstrap-Komponenten werden in unser Kit und nicht direkt in die Anwendungskomponenten übernommen.
In der Zwischenzeit haben wir unsere Mock-Daten mit dem Server-Ingenieur geteilt, der hart daran arbeitet, separate Server-Umgebungen aufzubauen, das Datenbankschema zu implementieren und uns einige Dienste zur Verfügung zu stellen.
Das gibt uns Zeit, unserer UI im nächsten Sprint etwas Glanz zu verleihen – was gut ist, denn die Entscheidungsträger möchten separate Themen für jedes Reiseziel sehen. Während der Benutzer durch die Reiseziele blättert, müssen wir das Farbschema der UI ändern, um dem angezeigten Reiseplakat zu entsprechen. Außerdem wollen wir versuchen, diese Komponenten etwas aufzuhübschen, um unseren eigenen Look and Feel zu entwickeln. Sobald wir etwas Geld verdienen, werden wir einen Designer für eine komplette Überholung engagieren, aber hoffentlich können wir für unsere frühen Nutzer einen glücklichen Mittelweg finden.
Sprint Two: Review Meeting
Wow! Das Team hat sich in diesem Sprint wirklich ins Zeug gelegt. Wir haben themenbezogene Themen, angepasste Komponenten und viele der verbleibenden React-Bootstrap-API-Implementierungen aus den Anwendungskomponenten entfernt.
Hier ist, wie der Desktop jetzt aussieht

Um dies zu ermöglichen, haben die Frontend-Entwickler die Styled Components Bibliothek eingeführt. Dies machte das Styling der einzelnen Kit-Komponenten zum Kinderspiel und ermöglichte die Unterstützung mehrerer Themen.
Werfen wir einen Blick auf einige Highlights ihrer Änderungen für diesen Sprint.
Erstens gibt es für globale Dinge wie das Einbinden von Schriftarten und das Festlegen der Seitenkörperstile eine neue Kit-Komponente namens KitGlobal.
// KitGlobal.js
import { createGlobalStyle } from "styled-components";
export const KitGlobal = createGlobalStyle`
body {
@import url('https://fonts.googleapis.com/css?family=Orbitron:500|Nunito:600|Alegreya+Sans+SC:700');
background-color: ${props => props.theme.foreground};
overflow-x: hidden;
}
`;
Sie verwendet den createGlobalStyle-Helfer, um die CSS für das Body-Element zu definieren. Dies importiert unsere gewünschten Webfonts von Google, setzt die Hintergrundfarbe auf den aktuellen "Foreground"-Wert des Themas und schaltet den Überlauf in x-Richtung aus, um eine lästige horizontale Scrollbar zu eliminieren. Wir verwenden diese KitGlobal-Komponente in der Render-Methode unserer App-Komponente.
Ebenfalls in der App-Komponente importieren wir ThemeProvider von styled-components und etwas namens "themes" aus ../theme. Wir verwenden Reacts useState, um das anfängliche Thema auf themes.luna zu setzen, und Reacts useEffect, um setTheme aufzurufen, wann immer sich die "destination" ändert. Die zurückgegebene Komponente ist nun in ThemeProvider verschachtelt, dem "theme" als Prop übergeben wird. Hier ist die App-Komponente in ihrer Gesamtheit.
// App.js
import React, { useState, useEffect } from "react";
import { ThemeProvider } from "styled-components";
import themes from "../theme/";
import { KitGlobal } from "../kit";
import Navigation from "./Navigation";
import Page from "./Page";
export default function App(props) {
const [destinationIndex, setDestinationIndex] = useState(0);
const [theme, setTheme] = useState(themes.luna);
const destination = props.destinations[destinationIndex];
useEffect(() => {
setTheme(themes[destination.theme]);
}, [destination]);
return (
<ThemeProvider theme={theme}>
<React.Fragment>
<KitGlobal />
<Navigation
{...props}
destinationIndex={destinationIndex}
setDestinationIndex={setDestinationIndex}
/>
<Page
{...props}
destinationIndex={destinationIndex}
setDestinationIndex={setDestinationIndex}
/>
</React.Fragment>
</ThemeProvider>
);
}
KitGlobal wird wie jede andere Komponente gerendert. Nichts Besonderes, nur dass das Body-Tag betroffen ist. ThemeProvider verwendet die React Context API, um theme an alle Komponenten weiterzugeben, die es benötigen (also alle). Um das vollständig zu verstehen, müssen wir uns auch ansehen, was ein Thema eigentlich ist.
Um ein Thema zu erstellen, hat einer unserer Frontend-Entwickler alle Reiseplakate genommen und für jedes Paletten erstellt, indem er die prominenten Farben extrahiert hat. Das war ziemlich einfach.

Offensichtlich würden wir nicht alle Farben verwenden. Der Ansatz bestand hauptsächlich darin, die zwei am häufigsten verwendeten Farben foreground und background zu nennen. Dann nahmen wir drei weitere Farben, die normalerweise von der hellsten zur dunkelsten geordnet sind: accent1, accent2 und accent3. Schließlich wählten wir zwei kontrastierende Farben, die wir text1 und text2 nannten. Für das obige Reiseziel sah das so aus
// theme/index.js (partial list)
const themes = {
...
mars: {
background: "#a53237",
foreground: "#f66f40",
accent1: "#f8986d",
accent2: "#9c4952",
accent3: "#f66f40",
text1: "#f5e5e1",
text2: "#354f55"
},
...
};
export default themes;
Sobald wir ein Thema für jedes Reiseziel haben und es an alle Komponenten weitergegeben wird (einschließlich der Kit-Komponenten, aus denen unsere Anwendungskomponenten jetzt aufgebaut sind), müssen wir styled-components verwenden, um diese Themenfarben sowie unsere benutzerdefinierten visuellen Stile wie die Panel-Ecken und den "Border Glow" anzuwenden.
Dies ist ein einfaches Beispiel, bei dem wir unsere KitHero-Komponente dazu gebracht haben, das Thema und benutzerdefinierte Stile auf das Bootstrap Jumbotron anzuwenden
// KitHero.js
import styled from "styled-components";
import { Jumbotron } from "react-bootstrap";
export const KitHero = styled(Jumbotron)`
background-color: ${props => props.theme.accent1};
color: ${props => props.theme.text2};
border-radius: 7px 25px;
border-color: ${props => props.theme.accent3};
border-style: solid;
border-width: 1px;
box-shadow: 0 0 1px 2px #fdb813, 0 0 3px 4px #f8986d;
font-family: "Nunito", sans-serif;
margin-bottom: 20px;
`;
In diesem Fall sind wir mit dem zufrieden, was styled-components zurückgibt, also nennen wir es einfach KitHero und exportieren es.
Wenn wir es in der Anwendung verwenden, sieht es so aus
// DestinationLayout.js (partial code)
const renderHero = () => {
return (
<KitHero>
<h2>{destination.header}</h2>
<p>{destination.blurb}</p>
<KitButton>Book Your Trip Now!</KitButton>
</KitHero>
);
};
Dann gibt es komplexere Fälle, in denen wir einige Attribute auf der React-Bootstrap-Komponente voreinstellen möchten. Zum Beispiel die KitNavbar-Komponente, die wir zuvor als mit vielen React-Bootstrap-Attributen identifiziert hatten, die wir lieber nicht von der Deklaration der Komponente in der Anwendung übergeben würden.
Nun zum Blick darauf, wie das gehandhabt wurde
// KitNavbar.js (partial code)
import React, { Component } from "react";
import styled from "styled-components";
import { Navbar } from "react-bootstrap";
const StyledBootstrapNavbar = styled(Navbar)`
background-color: ${props => props.theme.background};
box-shadow: 0 0 1px 2px #fdb813, 0 0 3px 4px #f8986d;
display: flex;
flex-direction: horizontal;
justify-content: space-between;
font-family: "Nunito", sans-serif;
`;
export class KitNavbar extends Component {
render() {
const { ...props } = this.props;
return <StyledBootstrapNavbar fixed="top" {...props} />;
}
}
Zuerst erstellen wir eine Komponente namens StyledBootstrapNavbar mit styled-components. Wir konnten einige Attribute mit dem CSS handhaben, das wir an styled-components übergeben haben. Aber um (vorerst) die zuverlässige Fixierung der Komponente am oberen Bildschirmrand beim Scrollen anderer Elemente weiter zu nutzen, haben sich unsere Frontend-Entwickler entschieden, weiterhin das fixed-Attribut von React-Bootstrap zu verwenden. Um dies zu tun, mussten wir eine KitNavbar-Komponente erstellen, die eine Instanz von StyledBootstrapNavbar mit dem Attribut fixed=top rendert. Wir haben auch alle Props durchgereicht, einschließlich der Kinder.
Wir müssen nur eine separate Klasse erstellen, die die Arbeit von styled-components rendert und Props weitergibt, wenn wir explizit einige Attribute in unserer Kit-Komponente als Standard festlegen wollen. In den meisten Fällen können wir einfach die Ausgabe von styled-components benennen und zurückgeben und sie wie mit KitHero oben verwenden.
Wenn wir nun die KitNavbar in der Navigation-Komponente unserer Anwendung rendern, sieht das so aus
// Navigation.js (partial code)
return (
<KitNavbar>
<KitNavbarBrand>
<KitLogo />
Solar Excursions
</KitNavbarBrand>
{renderDestinationMenu()}
</KitNavbar>
);
Schließlich haben wir unsere ersten Schritte unternommen, um unsere Kit-Komponenten von React-Bootstrap zu refaktorieren. Die KitAttribution-Komponente ist ein Bootstrap Alert, das für unsere Zwecke kaum mehr als ein gewöhnliches div ist. Wir konnten es leicht refaktorieren, um seine Abhängigkeit von React-Bootstrap zu entfernen.
Das ist die Komponente, wie sie aus dem vorherigen Sprint hervorgegangen ist
// KitAttribution.js (using react-bootstrap)
import { Alert } from "react-bootstrap";
export const KitAttribution = Alert;
Das ist, wie sie jetzt aussieht
// KitAttribution.js
import styled from "styled-components";
export const KitAttribution = styled.div`
text-align: center;
background-color: ${props => props.theme.accent1};
color: ${props => props.theme.text2};
border-radius: 7px 25px;
border-color: ${props => props.theme.accent3};
border-style: solid;
border-width: 1px;
box-shadow: 0 0 1px 2px #fdb813, 0 0 3px 4px #f8986d;
font-family: "Alegreya Sans SC", sans-serif;
> a {
color: ${props => props.theme.text2};
font-family: "Nunito", sans-serif;
}
> a:hover {
color: ${props => props.theme.background};
text-decoration-color: ${props => props.theme.accent3};
}
`;
Beachten Sie, wie wir React-Bootstrap nicht mehr importieren und styled.div als Komponentengrundlage verwenden. Sie werden nicht alle so einfach sein, aber es ist ein Prozess.
Hier sind die Ergebnisse der Styling- und Theming-Bemühungen unseres Teams in Sprint Two
Sehen Sie die thematische Seite separat hier.
Fazit
Nach drei Sprints ist unser Team auf dem besten Weg, eine skalierbare Komponentenarchitektur für die UI zu implementieren.
- Wir bewegen uns dank react-bootstrap schnell, aber stapeln dadurch keine großen technischen Schulden mehr.
- Dank styled-components konnten wir mehrere Themen implementieren (wie praktisch jede App im Internet heutzutage dunkle und helle Modi bietet). Außerdem sehen wir nicht mehr wie eine Standard-Bootstrap-App aus.
- Durch die Implementierung eines benutzerdefinierten Komponentensatzes, der alle Referenzen auf React-Bootstrap enthält, können wir uns im Laufe der Zeit davon refaktorieren.
Forken Sie den endgültigen Codebestand auf GitHub.
Können Sie beschreiben, worauf sich die folgenden Beispiele für styled components beziehen?
Hallo Marc,
Guter Punkt. Unter der Haube handelt es sich tatsächlich um einen CSS-Trick.
In styled-components wird das Ampersand durch den generierten Klassennamen ersetzt: https://www.styled-components.com/docs/api#supported-css
Und in CSS können Sie einen Klassennamen wiederholen, um die Spezifität zu erhöhen: https://www.w3.org/TR/selectors-3/#specificity
Wenn Sie also versuchen, bestehende Stile zu überschreiben, wie z.B. die von MenuItem in React-Bootstrap, und der von Ihnen definierte Stil den aus der Bibliothek tatsächlich nicht überschreibt, können Sie diesen Trick verwenden, um die Überschreibung zu erzwingen, anstatt auf !important zurückzugreifen (was manchmal selbst nicht wirksam ist). Sie können einfach weiter Ampersands hinzufügen, bis die Überschreibung funktioniert.
Dies ist natürlich eine Übergangslösung, bis Sie alle vorhandenen Stile refaktorieren (wenn Sie einer Trajektorie wie der im Artikel beschriebenen folgen).