Styled-Components: CSS-in-JS-Bibliothek für das moderne Web
Veröffentlicht: 2022-03-11CSS wurde für Dokumente entwickelt, die das „alte Web“ enthalten sollte. Das Aufkommen von Präprozessoren wie Sass oder Less zeigt, dass die Community mehr braucht als das, was CSS bietet. Da Web-Apps im Laufe der Zeit immer komplexer werden, werden die Beschränkungen von CSS immer sichtbarer und schwieriger zu mildern.
Styled-Components nutzt die Leistungsfähigkeit einer vollständigen Programmiersprache – JavaScript – und ihre Scoping-Funktionen, um den Code in Komponenten zu strukturieren. Dies hilft, die üblichen Fallstricke beim Schreiben und Verwalten von CSS für große Projekte zu vermeiden. Ein Entwickler kann den Stil einer Komponente ohne das Risiko von Nebenwirkungen beschreiben.
Was ist das Problem?
Ein Vorteil der Verwendung von CSS ist, dass der Stil vollständig vom Code entkoppelt ist. Das bedeutet, dass Entwickler und Designer parallel arbeiten können, ohne sich gegenseitig zu stören.
Andererseits machen gestylte Komponenten es einfacher, in die Falle zu tappen, Stil und Logik stark zu koppeln. Max Stoiber erklärt, wie man das vermeidet. Während die Idee, Logik und Präsentation zu trennen, definitiv nicht neu ist, könnte man versucht sein, bei der Entwicklung von React-Komponenten Abkürzungen zu nehmen. Beispielsweise ist es einfach, eine Komponente für eine Validierungsschaltfläche zu erstellen, die sowohl die Klickaktion als auch den Stil der Schaltfläche verarbeitet. Es erfordert etwas mehr Aufwand, es in zwei Komponenten aufzuteilen.
Die Container-/Präsentationsarchitektur
Das ist ein ziemlich einfaches Prinzip. Komponenten definieren entweder, wie die Dinge aussehen, oder sie verwalten Daten und Logik. Ein sehr wichtiger Aspekt von Präsentationskomponenten ist, dass sie niemals Abhängigkeiten haben sollten. Sie erhalten Requisiten und rendern DOM (oder Kinder) entsprechend. Container hingegen kennen die Datenarchitektur (State, Redux, Flux etc.), sollten aber nie für die Anzeige zuständig sein. Der Artikel von Dan Abramov ist eine sehr gute und detaillierte Erklärung dieser Architektur.
Erinnerung an SMACSS
Obwohl die skalierbare und modulare Architektur für CSS ein Stilleitfaden für die Organisation von CSS ist, ist das Grundkonzept eines, das zum größten Teil automatisch von Stilkomponenten befolgt wird. Die Idee ist, CSS in fünf Kategorien zu unterteilen:
- Base enthält alle allgemeinen Regeln.
- Das Layout dient dazu, die strukturellen Eigenschaften sowie verschiedene Inhaltsbereiche (z. B. Kopfzeile, Fußzeile, Seitenleiste, Inhalt) zu definieren.
- Modul enthält Unterkategorien für die verschiedenen logischen Blöcke der Benutzeroberfläche.
- State definiert Modifikatorklassen, um den Status von Elementen anzuzeigen, z. B. fehlerhaftes Feld, deaktivierte Schaltfläche.
- Das Design enthält Farbe, Schriftart und andere kosmetische Aspekte, die modifizierbar oder von Benutzereinstellungen abhängig sein können.
Es ist einfach, diese Trennung bei der Verwendung von Stilkomponenten beizubehalten. Projekte beinhalten normalerweise eine Art CSS-Normalisierung oder -Zurücksetzung. Dies fällt normalerweise in die Basiskategorie . Sie können auch allgemeine Schriftgrößen, Zeilengrößen usw. definieren. Dies kann über normales CSS (oder Sass/Less) oder über die von styled-components bereitgestellte injectGlobal
-Funktion erfolgen.
Wenn Sie für Layoutregeln ein UI-Framework verwenden, wird es wahrscheinlich Containerklassen oder ein Grid-System definieren. Sie können diese Klassen problemlos in Verbindung mit Ihren eigenen Regeln in den von Ihnen geschriebenen Layoutkomponenten verwenden.
Dem Modul folgt automatisch die Architektur von styled-components , da die Stile direkt an Komponenten angehängt und nicht in externen Dateien beschrieben werden. Grundsätzlich ist jede gestaltete Komponente, die Sie schreiben, ein eigenes Modul. Sie können Ihren Styling-Code schreiben, ohne sich Gedanken über Nebenwirkungen machen zu müssen.
Status sind Regeln, die Sie in Ihren Komponenten als variable Regeln definieren. Sie definieren einfach eine Funktion, um Werte Ihrer CSS-Attribute zu interpolieren. Wenn Sie ein UI-Framework verwenden, haben Sie möglicherweise auch nützliche Klassen, die Sie Ihren Komponenten hinzufügen können. Sie werden wahrscheinlich auch CSS-Pseudoselektorregeln haben (Hover, Fokus usw.)
Das Theme kann einfach in Ihre Komponenten interpoliert werden. Es ist eine gute Idee, Ihr Thema als eine Reihe von Variablen zu definieren, die in Ihrer gesamten Anwendung verwendet werden. Sie können Farben sogar programmgesteuert (unter Verwendung einer Bibliothek oder manuell) ableiten, um beispielsweise Kontraste und Glanzlichter zu behandeln. Denken Sie daran, dass Ihnen die volle Leistungsfähigkeit einer Programmiersprache zur Verfügung steht!
Bringen Sie sie für eine Lösung zusammen
Für eine einfachere Navigation ist es wichtig, sie zusammenzuhalten; Wir wollen sie nicht nach Typ (Darstellung vs. Logik) organisieren, sondern nach Funktionalität.
Somit haben wir einen Ordner für alle generischen Komponenten (Schaltflächen und dergleichen). Die anderen sollten je nach Projekt und dessen Funktionalitäten organisiert werden. Wenn wir beispielsweise Benutzerverwaltungsfunktionen haben, sollten wir alle für diese Funktion spezifischen Komponenten gruppieren.
Um die Container-/Präsentationsarchitektur von styled-components auf einen SMACSS-Ansatz anzuwenden, benötigen wir einen zusätzlichen Komponententyp: strukturell. Am Ende haben wir drei Arten von Komponenten; gestylt, strukturell und Container. Da styled-components ein Tag (oder eine Komponente) schmücken, benötigen wir diesen dritten Komponententyp, um das DOM zu strukturieren. In einigen Fällen kann es möglich sein, einer Containerkomponente zu erlauben, die Struktur von Unterkomponenten zu handhaben, aber wenn die DOM-Struktur komplex wird und für visuelle Zwecke erforderlich ist, ist es am besten, sie zu trennen. Ein gutes Beispiel ist eine Tabelle, in der das DOM normalerweise ziemlich ausführlich wird.

Beispielprojekt
Lassen Sie uns eine kleine App erstellen, die Rezepte anzeigt, um diese Prinzipien zu veranschaulichen. Wir können mit dem Erstellen einer Recipes-Komponente beginnen. Die übergeordnete Komponente ist ein Controller. Es behandelt den Status – in diesem Fall die Liste der Rezepte. Es ruft auch eine API-Funktion auf, um die Daten abzurufen.
class Recipes extends Component{ constructor (props) { super(props); this.state = { recipes: [] }; } componentDidMount () { this.loadData() } loadData () { getRecipes().then(recipes => { this.setState({recipes}) }) } render() { let {recipes} = this.state return ( <RecipesContainer recipes={recipes} /> ) } }
Es wird die Liste der Rezepte rendern, aber es muss (und sollte) nicht wissen, wie. Also rendern wir eine weitere Komponente, die die Liste der Rezepte erhält und DOM ausgibt:
class RecipesContainer extends Component{ render() { let {recipes} = this.props return ( <TilesContainer> {recipes.map(recipe => (<Recipe key={recipe.id} {...recipe}/>))} </TilesContainer> ) } }
Hier wollen wir eigentlich ein Kachelraster machen. Es kann eine gute Idee sein, das eigentliche Kachellayout zu einer generischen Komponente zu machen. Wenn wir das also extrahieren, erhalten wir eine neue Komponente, die so aussieht:
class TilesContainer extends Component { render () { let {children} = this.props return ( <Tiles> { React.Children.map(children, (child, i) => ( <Tile key={i}> {child} </Tile> )) } </Tiles> ) } }
TilesStyles.js:
export const Tiles = styled.div` padding: 20px 10px; display: flex; flex-direction: row; flex-wrap: wrap; ` export const Tile = styled.div` flex: 1 1 auto; ... display: flex; & > div { flex: 1 0 auto; } `
Beachten Sie, dass diese Komponente nur der Präsentation dient. Es definiert seinen Stil und verpackt alle untergeordneten Elemente, die es erhält, in ein anderes gestaltetes DOM-Element, das definiert, wie Kacheln aussehen. Es ist ein gutes Beispiel dafür, wie Ihre generischen Präsentationskomponenten architektonisch aussehen werden.
Dann müssen wir definieren, wie ein Rezept aussieht. Wir brauchen eine Container-Komponente, um das relativ komplexe DOM zu beschreiben und gegebenenfalls den Stil zu definieren. Wir enden damit:
class RecipeContainer extends Component { onChangeServings (e) { let {changeServings} = this.props changeServings(e.target.value) } render () { let {title, ingredients, instructions, time, servings} = this.props return ( <Recipe> <Title>{title}</Title> <div>{time}</div> <div>Serving <input type="number" min="1" max="1000" value={servings} onChange={this.onChangeServings.bind(this)}/> </div> <Ingredients> {ingredients.map((ingredient, i) => ( <Ingredient key={i} servings={servings}> <span className="name">{ingredient.name}</span> <span className="quantity">{ingredient.quantity * servings} {ingredient.unit}</span> </Ingredient> ))} </Ingredients> <div> {instructions.map((instruction, i) => (<p key={i}>{instruction}</p>))} </div> </Recipe> ) } }
Beachten Sie hier, dass der Container eine DOM-Generierung durchführt, aber dies die einzige Logik ist, die er enthält. Denken Sie daran, dass Sie verschachtelte Stile definieren können, sodass Sie nicht für jedes Tag, das Stile erfordert, ein Stilelement erstellen müssen. Das machen wir hier für den Namen und die Menge der Zutat. Natürlich könnten wir es weiter aufteilen und eine neue Komponente für eine Zutat erstellen. Das bestimmen Sie – je nach Projektkomplexität – über die Granularität. In diesem Fall ist es nur eine gestylte Komponente, die zusammen mit dem Rest in der RecipeStyles-Datei definiert wird:
export const Recipe = styled.div` background-color: ${theme('colors.background-highlight')}; `; export const Title = styled.div` font-weight: bold; ` export const Ingredients = styled.ul` margin: 5px 0; ` export const Ingredient = styled.li` & .name { ... } & .quantity { ... } `
Für diese Übung habe ich den ThemeProvider verwendet. Es injiziert das Thema in die Requisiten von gestylten Komponenten. Sie können es einfach als color: ${props => props.theme.core_color}
, ich verwende nur einen kleinen Wrapper, um vor fehlenden Attributen im Thema zu schützen:
const theme = (key) => (prop) => _.get(prop.theme, key) || console.warn('missing key', key)
Sie können auch Ihre eigenen Konstanten in einem Modul definieren und diese stattdessen verwenden. Zum Beispiel: color: ${styleConstants.core_color}
Vorteile
Ein Vorteil der Verwendung von Stilkomponenten ist, dass Sie sie so wenig verwenden können, wie Sie möchten. Sie können Ihr bevorzugtes UI-Framework verwenden und darüber gestaltete Komponenten hinzufügen. Dies bedeutet auch, dass Sie ein bestehendes Projekt problemlos komponentenweise migrieren können. Sie können den größten Teil des Layouts mit Standard-CSS gestalten und styled-components nur für wiederverwendbare Komponenten verwenden.
Nachteile
Designer/Stilintegratoren müssen sehr einfaches JavaScript lernen, um mit Variablen umzugehen und sie anstelle von Sass/Less zu verwenden.
Sie müssen auch lernen, in der Projektstruktur zu navigieren, obwohl ich argumentieren würde, dass es einfacher ist, die Stile für eine Komponente im Ordner dieser Komponente zu finden, als die richtige CSS/Sass/Less-Datei zu finden, die die Regel enthält, die Sie ändern müssen.
Sie müssen auch ihre Tools ein wenig ändern, wenn sie Syntax-Highlighting, Linting usw. wollen. Ein guter Anfang ist mit diesem Atom-Plugin und diesem Babel-Plugin.