Styled-Components : bibliothèque CSS-in-JS pour le Web moderne
Publié: 2022-03-11CSS a été conçu pour les documents, ce que l'« ancien Web » devait contenir. L'émergence de préprocesseurs comme Sass ou Less montre que la communauté a besoin de plus que ce que propose CSS. Les applications Web devenant de plus en plus complexes au fil du temps, les limites de CSS sont devenues de plus en plus visibles et difficiles à atténuer.
Styled-components exploite la puissance d'un langage de programmation complet, JavaScript, et ses capacités de portée pour aider à structurer le code en composants. Cela permet d'éviter les pièges courants de l'écriture et de la maintenance de CSS pour les grands projets. Un développeur peut décrire le style d'un composant sans risque d'effets secondaires.
Quel est le problème?
L'un des avantages de l'utilisation de CSS est que le style est complètement découplé du code. Cela signifie que les développeurs et les concepteurs peuvent travailler en parallèle sans interférer les uns avec les autres.
D'autre part, les composants stylés permettent de tomber plus facilement dans le piège du couplage fort du style et de la logique. Max Stoiber explique comment éviter cela. Bien que l'idée de séparer la logique et la présentation ne soit certainement pas nouvelle, on pourrait être tenté de prendre des raccourcis lors du développement de composants React. Par exemple, il est facile de créer un composant pour un bouton de validation qui gère l'action de clic ainsi que le style du bouton. Il faut un peu plus d'efforts pour le diviser en deux composants.
L'architecture conteneur/présentationnelle
C'est un principe assez simple. Les composants définissent l'apparence des choses ou gèrent les données et la logique. Un aspect très important des composants de présentation est qu'ils ne doivent jamais avoir de dépendances. Ils reçoivent des accessoires et rendent DOM (ou enfants) en conséquence. Les conteneurs, en revanche, connaissent l'architecture des données (état, redux, flux, etc.) mais ne doivent jamais être responsables de l'affichage. L'article de Dan Abramov est une très bonne explication détaillée de cette architecture.
Se souvenir du SMACSS
Bien que l'architecture évolutive et modulaire pour CSS soit un guide de style pour l'organisation de CSS, le concept de base est celui qui est suivi, pour la plupart automatiquement, par les composants stylés. L'idée est de séparer les CSS en cinq catégories :
- Base contient toutes les règles générales.
- La mise en page a pour but de définir les propriétés structurelles ainsi que les différentes sections de contenu (en-tête, pied de page, barre latérale, contenu, par exemple).
- Le module contient des sous-catégories pour les différents blocs logiques de l'interface utilisateur.
- State définit les classes de modificateurs pour indiquer les états des éléments, par exemple champ en erreur, bouton désactivé.
- Le thème contient la couleur, la police et d'autres aspects cosmétiques qui peuvent être modifiables ou dépendent des préférences de l'utilisateur.
Garder cette séparation tout en utilisant des composants de style est facile. Les projets incluent généralement une sorte de normalisation ou de réinitialisation CSS. Cela tombe généralement dans la catégorie de base . Vous pouvez également définir la taille générale de la police, la taille des lignes, etc. Cela peut être fait via CSS normal (ou Sass/Less), ou via la fonction injectGlobal
fournie par styled -components .
Pour les règles de mise en page , si vous utilisez un framework d'interface utilisateur, il définira probablement des classes de conteneurs ou un système de grille. Vous pouvez facilement utiliser ces classes conjointement avec vos propres règles dans les composants de mise en page que vous écrivez.
Le module est automatiquement suivi de l'architecture de styled-components , puisque les styles sont attachés directement aux composants, plutôt que décrits dans des fichiers externes. Fondamentalement, chaque composant stylisé que vous écrivez sera son propre module. Vous pouvez écrire votre code de style sans vous soucier des effets secondaires.
L'état sera les règles que vous définissez dans vos composants en tant que règles variables. Vous définissez simplement une fonction pour interpoler les valeurs de vos attributs CSS. Si vous utilisez un framework d'interface utilisateur, vous pouvez également avoir des classes utiles à ajouter à vos composants. Vous aurez probablement aussi des règles de pseudo-sélecteur CSS (survol, focus, etc.)
Le thème peut simplement être interpolé dans vos composants. C'est une bonne idée de définir votre thème comme un ensemble de variables à utiliser tout au long de votre application. Vous pouvez même dériver des couleurs par programme (à l'aide d'une bibliothèque ou manuellement), par exemple pour gérer les contrastes et les reflets. N'oubliez pas que vous disposez de toute la puissance d'un langage de programmation !
Rassemblez-les pour une solution
Il est important de les garder ensemble, pour une expérience de navigation plus facile ; Nous ne voulons pas les organiser par type (présentation vs. logique) mais plutôt par fonctionnalité.
Ainsi, nous aurons un dossier pour tous les composants génériques (boutons et autres). Les autres doivent être organisés en fonction du projet et de ses fonctionnalités. Par exemple, si nous avons des fonctionnalités de gestion des utilisateurs, nous devons regrouper tous les composants spécifiques à cette fonctionnalité.
Pour appliquer l'architecture conteneur/présentation des composants de style à une approche SMACSS, nous avons besoin d'un type de composant supplémentaire : structurel. Nous nous retrouvons avec trois types de composants ; stylisé, structurel et contenant. Étant donné que styled-components décore une balise (ou un composant), nous avons besoin de ce troisième type de composant pour structurer le DOM. Dans certains cas, il peut être possible d'autoriser un composant de conteneur à gérer la structure des sous-composants, mais lorsque la structure DOM devient complexe et est nécessaire à des fins visuelles, il est préférable de les séparer. Un bon exemple est une table, où le DOM devient généralement assez verbeux.

Exemple de projet
Construisons une petite application qui affiche des recettes pour illustrer ces principes. Nous pouvons commencer à créer un composant Recettes. Le composant parent sera un contrôleur. Il gérera l'état, dans ce cas, la liste des recettes. Il appellera également une fonction API pour récupérer les données.
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} /> ) } }
Il affichera la liste des recettes, mais il n'a pas (et ne devrait pas) avoir besoin de savoir comment. Nous rendons donc un autre composant qui récupère la liste des recettes et produit le DOM :
class RecipesContainer extends Component{ render() { let {recipes} = this.props return ( <TilesContainer> {recipes.map(recipe => (<Recipe key={recipe.id} {...recipe}/>))} </TilesContainer> ) } }
Ici, en fait, nous voulons faire une grille de tuiles. Il peut être judicieux de faire de la disposition réelle des tuiles un composant générique. Donc, si nous extrayons cela, nous obtenons un nouveau composant qui ressemble à ceci :
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; } `
Notez que ce composant est purement de présentation. Il définit son style et encapsule tous les enfants qu'il reçoit dans un autre élément DOM stylisé qui définit à quoi ressemblent les tuiles. C'est un bon exemple de ce à quoi ressembleront vos composants de présentation génériques sur le plan architectural.
Ensuite, nous devons définir à quoi ressemble une recette. Nous avons besoin d'un composant de conteneur pour décrire le DOM relativement complexe ainsi que pour définir le style si nécessaire. On finit par ça :
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> ) } }
Notez ici que le conteneur effectue une génération DOM, mais c'est la seule logique qu'il contient. N'oubliez pas que vous pouvez définir des styles imbriqués, vous n'avez donc pas besoin de créer un élément stylé pour chaque balise nécessitant un style. C'est ce que nous faisons ici pour le nom et la quantité de l'ingrédient. Bien sûr, nous pourrions le diviser davantage et créer un nouveau composant pour un ingrédient. C'est à vous, selon la complexité du projet, de déterminer la granularité. Dans ce cas, il s'agit simplement d'un composant stylé défini avec le reste dans le fichier RecipeStyles :
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 { ... } `
Pour les besoins de cet exercice, j'ai utilisé le ThemeProvider. Il injecte le thème dans les accessoires des composants stylés. Vous pouvez simplement l'utiliser comme color: ${props => props.theme.core_color}
, j'utilise juste un petit wrapper pour me protéger des attributs manquants dans le thème :
const theme = (key) => (prop) => _.get(prop.theme, key) || console.warn('missing key', key)
Vous pouvez également définir vos propres constantes dans un module et les utiliser à la place. Par exemple : color: ${styleConstants.core_color}
Avantages
L'avantage d'utiliser des composants stylés est que vous pouvez l'utiliser aussi peu que vous le souhaitez. Vous pouvez utiliser votre framework d'interface utilisateur préféré et y ajouter des composants stylés . Cela signifie également que vous pouvez facilement migrer un projet existant composant par composant. Vous pouvez choisir de styliser la majeure partie de la mise en page avec le CSS standard et n'utiliser que des composants stylés pour les composants réutilisables.
Les inconvénients
Les concepteurs/intégrateurs de style devront apprendre JavaScript très basique pour gérer les variables et les utiliser à la place de Sass/Less.
Ils devront également apprendre à naviguer dans la structure du projet, bien que je dirais qu'il est plus facile de trouver les styles d'un composant dans le dossier de ce composant que d'avoir à trouver le bon fichier CSS/Sass/Less contenant la règle que vous devez modifier.
Ils devront également modifier un peu leurs outils s'ils veulent la coloration syntaxique, le linting, etc. Un bon point de départ est avec ce plugin Atom et ce plugin babel.