Comment utiliser les composants stylés dans React

Publié: 2022-03-10
Résumé rapide ↬ Alors que l'approche basée sur les composants a inauguré une nouvelle frontière dans la façon dont nous construisons des applications Web, elle n'est pas sans imperfections, l'une étant sa convivialité et son évolutivité avec CSS. Cela a donné naissance à une nouvelle façon de construire et de gérer nos styles de manière spécifique aux composants , autrement connue sous le nom de CSS-in-JS.

Les composants stylisés sont un outil CSS-in-JS qui comble le fossé entre les composants et le style, offrant de nombreuses fonctionnalités pour vous permettre d'être opérationnel dans les composants de style de manière fonctionnelle et réutilisable. Dans cet article, vous apprendrez les bases des composants stylisés et comment les appliquer correctement à vos applications React. Vous devriez avoir travaillé sur React auparavant avant de suivre ce tutoriel. Si vous recherchez diverses options dans le style des composants React, vous pouvez consulter notre article précédent sur le sujet.

Au cœur de CSS se trouve la capacité de cibler n'importe quel élément HTML - globalement - quelle que soit sa position dans l'arborescence DOM. Cela peut être un obstacle lorsqu'il est utilisé avec des composants, car les composants exigent, dans une mesure raisonnable, une colocation (c'est-à-dire en gardant les actifs tels que les états et le style) plus près de l'endroit où ils sont utilisés (appelée localisation).

Selon les propres termes de React, les composants stylisés sont des « primitives visuelles pour les composants », et leur objectif est de nous donner un moyen flexible de styliser les composants. Le résultat est un couplage étroit entre les composants et leurs styles.

Remarque : les composants stylés sont disponibles à la fois pour React et React Native, et bien que vous deviez absolument consulter le guide React Native, nous nous concentrerons ici sur les composants stylés pour React.

Plus après saut! Continuez à lire ci-dessous ↓

Pourquoi des composants stylés ?

En plus de vous aider à déterminer la portée des styles, les composants stylés incluent les fonctionnalités suivantes :

  • Préfixe automatique du fournisseur
    Vous pouvez utiliser des propriétés CSS standard et les composants stylisés ajouteront des préfixes de fournisseur si nécessaire.
  • Noms de classe uniques
    Les composants stylés sont indépendants les uns des autres et vous n'avez pas à vous soucier de leurs noms car la bibliothèque s'en charge pour vous.
  • Élimination des styles morts
    Les composants stylés suppriment les styles inutilisés, même s'ils sont déclarés dans votre code.
  • et beaucoup plus.

Installation

L'installation de composants stylés est facile. Vous pouvez le faire via un CDN ou avec un gestionnaire de packages tel que Yarn…

 yarn add styled-components

… ou npm :

 npm i styled-components

Notre démo utilise create-react-app.

Commençant

Peut-être que la première chose que vous remarquerez à propos des composants stylés est leur syntaxe, qui peut être intimidante si vous ne comprenez pas la magie derrière les composants stylés. Pour le dire brièvement, les composants stylés utilisent les littéraux de modèle de JavaScript pour combler le fossé entre les composants et les styles. Ainsi, lorsque vous créez un composant stylé, vous créez en fait un composant React avec des styles. Il ressemble à ceci :

 import styled from "styled-components"; // Styled component named StyledButton const StyledButton = styled.button` background-color: black; font-size: 32px; color: white; `; function Component() { // Use it like any other component. return <StyledButton> Login </StyledButton>; }

Ici, StyledButton est le composant stylé, et il sera rendu sous la forme d'un bouton HTML avec les styles contenus. styled est une méthode utilitaire interne qui transforme le style de JavaScript en véritable CSS.

En HTML brut et CSS, nous aurions ceci :

 button { background-color: black; font-size: 32px; color: white; } <button> Login </button>

Si les composants stylés sont des composants React, pouvons-nous utiliser des accessoires ? Oui nous pouvons.

Adaptation basée sur les accessoires

Les composants stylisés sont fonctionnels , nous pouvons donc facilement styliser les éléments de manière dynamique. Supposons que nous ayons deux types de boutons sur notre page, l'un avec un fond noir et l'autre bleu. Nous n'avons pas besoin de créer deux composants stylés pour eux ; nous pouvons adapter leur style en fonction de leurs accessoires.

 import styled from "styled-components"; const StyledButton = styled.button` min-width: 200px; border: none; font-size: 18px; padding: 7px 10px; /* The resulting background color will be based on the bg props. */ background-color: ${props => props.bg === "black" ? "black" : "blue"; `; function Profile() { return ( <div> <StyledButton bg="black">Button A</StyledButton> <StyledButton bg="blue">Button B</StyledButton> </div> ) }

Étant donné que StyledButton est un composant React qui accepte les accessoires, nous pouvons attribuer une couleur d'arrière-plan différente en fonction de l'existence ou de la valeur de l'accessoire bg .

Vous remarquerez cependant que nous n'avons pas donné de type à notre bouton . Faisons cela:

 function Profile() { return ( <> <StyledButton bg="black" type="button"> Button A </StyledButton> <StyledButton bg="blue" type="submit" onClick={() => alert("clicked")}> Button B </StyledButton> </> ); }

Les composants stylisés peuvent différencier les types d'accessoires qu'ils reçoivent. Ils savent que type est un attribut HTML, donc ils rendent en fait <button type="button">Button A</button> , tout en utilisant la prop bg dans leur propre traitement. Remarquez comment nous avons également attaché un gestionnaire d'événements ?

En parlant d'attributs, une syntaxe étendue nous permet de gérer les accessoires à l'aide du constructeur attrs . Regarde ça:

 const StyledContainer = styled.section.attrs((props) => ({ width: props.width || "100%", hasPadding: props.hasPadding || false, }))` --container-padding: 20px; width: ${(props) => props.width}; // Falls back to 100% padding: ${(props) => (props.hasPadding && "var(--container-padding)") || "none"}; `;

Remarquez que nous n'avons pas besoin d'un ternaire lors du réglage de la largeur ? C'est parce que nous lui avons déjà défini une width: props.width || "100%", width: props.width || "100%", . De plus, nous avons utilisé les propriétés personnalisées CSS parce que nous le pouvons !

Remarque : Si les composants stylés sont des composants React et que nous pouvons transmettre des accessoires, pouvons-nous également utiliser des états ? Le compte GitHub de la bibliothèque a un problème concernant ce problème.

Étendre les styles

Disons que vous travaillez sur une page de destination et que vous avez défini votre conteneur sur une certaine largeur maximale pour que les choses restent centrées. Vous avez un StyledContainer pour cela :

 const StyledContainer = styled.section` max-width: 1024px; padding: 0 20px; margin: 0 auto; `;

Ensuite, vous découvrez que vous avez besoin d'un conteneur plus petit, avec un rembourrage de 10 pixels des deux côtés, au lieu de 20 pixels. Votre première pensée pourrait être de créer un autre composant stylé, et vous auriez raison, mais cela ne prendrait pas de temps avant que vous réalisiez que vous dupliquez des styles.

 const StyledContainer = styled.section` max-width: 1024px; padding: 0 20px; margin: 0 auto; `; const StyledSmallContainer = styled.section` max-width: 1024px; padding: 0 10px; margin: 0 auto; `;

Avant de continuer et de créer StyledSmallContainer , comme dans l'extrait ci-dessus, apprenons comment réutiliser et hériter des styles. C'est plus ou moins comme le fonctionnement de l'opérateur de spread :

 const StyledContainer = styled.section` max-width: 1024px; padding: 0 20px; margin: 0 auto; `; // Inherit StyledContainer in StyledSmallConatiner const StyledSmallContainer = styled(StyledContainer)` padding: 0 10px; `; function Home() { return ( <StyledContainer> <h1>The secret is to be happy</h1> </StyledContainer> ); } function Contact() { return ( <StyledSmallContainer> <h1>The road goes on and on</h1> </StyledSmallContainer> ); }

Dans votre StyledSmallContainer , vous obtiendrez tous les styles de StyledContainer , mais le rembourrage sera remplacé. Gardez à l'esprit que, normalement, vous obtiendrez un élément de section rendu pour StyledSmallContainer , car c'est ce que StyledContainer rend. Mais cela ne veut pas dire qu'il est gravé dans la pierre ou immuable.

L'accessoire polymorphe "comme"

Avec le prop polymorphe as , vous pouvez échanger l'élément final qui est rendu. Un cas d'utilisation est lorsque vous héritez de styles (comme dans le dernier exemple). Si, par exemple, vous préférez un div à une section pour StyledSmallContainer , vous pouvez passer as prop à votre composant stylé avec la valeur de votre élément préféré, comme ceci :

 function Home() { return ( <StyledContainer> <h1>It's business, not personal</h1> </StyledContainer> ); } function Contact() { return ( <StyledSmallContainer as="div"> <h1>Never dribble when you can pass</h1> </StyledSmallContainer> ); }

Maintenant, StyledSmallContainer sera rendu sous la forme d'un div . Vous pourriez même avoir un composant personnalisé comme valeur :

 function Home() { return ( <StyledContainer> <h1>It's business, not personal</h1> </StyledContainer> ); } function Contact() { return ( <StyledSmallContainer as={StyledContainer}> <h1>Never dribble when you can pass</h1> </StyledSmallContainer> ); }

Ne le prenez pas pour acquis.

Syntaxe de type SCSS

Le préprocesseur CSS Stylis permet aux composants stylisés de prendre en charge une syntaxe de type SCSS, telle que l'imbrication :

 const StyledProfileCard = styled.div` border: 1px solid black; > .username { font-size: 20px; color: black; transition: 0.2s; &:hover { color: red; } + .dob { color: grey; } } `; function ProfileCard() { return ( <StyledProfileCard> <h1 className="username">John Doe</h1> <p className="dob"> Date: <span>12th October, 2013</span> </p> <p className="gender">Male</p> </StyledProfileCard> ); }

Animation

Les composants stylisés ont un assistant d' keyframes qui aide à construire des images clés d'animation (réutilisables). L'avantage ici est que les images clés seront détachées des composants stylés et pourront être exportées et réutilisées partout où cela sera nécessaire.

 import styled, {keyframes} from "styled-components"; const slideIn = keyframes` from { opacity: 0; } to { opacity: 1; } `; const Toast = styled.div` animation: ${slideIn} 0.5s cubic-bezier(0.4, 0, 0.2, 1) both; border-radius: 5px; padding: 20px; position: fixed; `;

Style global

Bien que l'objectif initial de CSS-in-JS et, par extension, des composants stylés soit la portée des styles, nous pouvons également tirer parti du style global des composants stylés. Étant donné que nous travaillons principalement avec des styles étendus, vous pourriez penser qu'il s'agit d'un paramètre d'usine invariable, mais vous vous trompez. Pensez-y : qu'est-ce que le cadrage ? Il nous est techniquement possible, au nom du style global, de faire quelque chose de similaire :

 ReactDOM.render( <StyledApp> <App /> </StyledApp>, document.getElementById("root") );

Mais nous avons déjà une fonction d'assistance - createGlobalStyle - dont la seule raison d'être est le style global. Alors, pourquoi lui dénier sa responsabilité ?

Une chose pour laquelle nous pouvons utiliser createGlobalStyle est de normaliser le CSS :

 import {createGlobalStyle} from "styled-components"; const GlobalStyle = createGlobalStyle` /* Your css reset here */ `; // Use your GlobalStyle function App() { return ( <div> <GlobalStyle /> <Routes /> </div> ); }

Remarque : Les styles créés avec createGlobalStyle n'acceptent aucun enfant. En savoir plus dans la documentation.

À ce stade, vous vous demandez peut-être pourquoi nous devrions nous embêter à utiliser createGlobalStlye . Voici quelques raisons:

  • Nous ne pouvons rien cibler en dehors du rendu racine sans lui (par exemple, html , body , etc.).
  • createGlobalStyle injecte des styles mais ne restitue aucun élément réel. Si vous regardez attentivement le dernier exemple, vous remarquerez que nous n'avons spécifié aucun élément HTML à afficher. C'est cool parce que nous n'avons peut-être pas réellement besoin de l'élément. Après tout, nous sommes concernés par les styles globaux. Nous ciblons les sélecteurs au sens large, pas des éléments spécifiques.
  • createGlobalStyle n'est pas délimité et peut être rendu n'importe où dans notre application et sera applicable tant qu'il se trouve dans le DOM. Pensez au concept , pas à la structure .
 import {createGlobalStyle} from "styled-components"; const GlobalStyle = createGlobalStyle` /* Your css reset here */ .app-title { font-size: 40px; } `; const StyledNav = styled.nav` /* Your styles here */ `; function Nav({children}) { return ( <StyledNav> <GlobalStyle /> {children} </StyledNav> ); } function App() { return ( <div> <Nav> <h1 className="app-title">STYLED COMPONENTS</h1> </Nav> <Main /> <Footer /> </div> ); }

Si vous pensez à la structure, app-title ne doit pas être stylisé comme défini dans GlobalStyle . Mais ça ne marche pas comme ça. Où que vous choisissiez de rendre votre GlobalStyle , il sera injecté lors du rendu de votre composant .

Attention : createGlobalStyles ne sera rendu que si et quand il est dans le DOM.

Aide CSS

Nous avons déjà vu comment adapter les styles en fonction des accessoires. Et si on voulait aller un peu plus loin ? La fonction d'assistance CSS aide à y parvenir. Supposons que nous ayons deux champs de saisie de texte avec des états : vide et actif, chacun avec une couleur différente. Nous pouvons le faire:

 const StyledTextField = styled.input` color: ${(props) => (props.isEmpty ? "none" : "black")}; `;

Tout va bien. Par la suite, si nous devons ajouter un autre état de remplissage, nous devrons modifier nos styles :

 const StyledTextField = styled.input` color: ${(props) => props.isEmpty ? "none" : props.active ? "purple" : "blue"}; `;

Or l'opération ternaire gagne en complexité. Et si nous ajoutions un autre état à nos champs de saisie de texte plus tard ? Ou que se passe-t-il si nous voulons donner à chaque état des styles supplémentaires, autres que la couleur ? Pouvez-vous imaginer enfermer les styles dans l'opération ternaire ? L'assistant css est très pratique.

 const StyledTextField = styled.input` width: 100%; height: 40px; ${(props) => (props.empty && css` color: none; backgroundcolor: white; `) || (props.active && css` color: black; backgroundcolor: whitesmoke; `)} `;

Ce que nous avons fait, c'est en quelque sorte étendre notre syntaxe ternaire pour accueillir plus de styles, et avec une syntaxe plus compréhensible et organisée. Si la déclaration précédente semble fausse, c'est parce que le code essaie d'en faire trop. Alors, revenons en arrière et affinons:

 const StyledTextField = styled.input` width: 100%; height: 40px; // 1. Empty state ${(props) => props.empty && css` color: none; backgroundcolor: white; `} // 2. Active state ${(props) => props.active && css` color: black; backgroundcolor: whitesmoke; `} // 3. Filled state ${(props) => props.filled && css` color: black; backgroundcolor: white; border: 1px solid green; `} `;

Notre raffinement divise le style en trois morceaux gérables et faciles à comprendre. C'est une victoire.

Gestionnaire de feuilles de style

Comme l'assistant CSS, StyleSheetManager est une méthode d'assistance pour modifier la façon dont les styles sont traités. Il faut certains accessoires - comme disableVendorPrefixes (vous pouvez consulter la liste complète) - qui vous aident à désactiver les préfixes de fournisseur de sa sous-arborescence.

 import styled, {StyleSheetManager} from "styled-components"; const StyledCard = styled.div` width: 200px; backgroundcolor: white; `; const StyledNav = styled.div` width: calc(100% - var(--side-nav-width)); `; function Profile() { return ( <div> <StyledNav /> <StyleSheetManager disableVendorPrefixes> <StyledCard> This is a card </StyledCard> </StyleSheetManager> </div> ); }

disableVendorPrefixes est transmis en tant que prop à <StyleSheetManager> . Ainsi, les composants stylés enveloppés par <StyleSheetManager> seraient désactivés, mais pas ceux de <StyledNav> .

Débogage plus facile

Lors de la présentation de composants stylés à l'un de mes collègues, l'une de leurs plaintes était qu'il était difficile de localiser un élément rendu dans le DOM - ou dans React Developer Tools, d'ailleurs. C'est l'un des inconvénients des composants stylés : en essayant de fournir des noms de classe uniques, il attribue des hachages uniques aux éléments, qui se trouvent être cryptés, mais cela rend le displayName lisible pour un débogage plus facile.

 import React from "react"; import styled from "styled-components"; import "./App.css"; const LoginButton = styled.button` background-color: white; color: black; border: 1px solid red; `; function App() { return ( <div className="App"> <LoginButton>Login</LoginButton> </div> ); }

Par défaut, les composants stylés rendent LoginButton en tant que <button class="LoginButton-xxxx xxxx">Login</button> dans le DOM, et en tant que LoginButton dans React Developer Tools, ce qui facilite le débogage. Nous pouvons basculer le booléen displayName si nous ne voulons pas ce comportement. Cela nécessite une configuration Babel.

Note : Dans la documentation, le package babel-plugin-styled-components est spécifié, ainsi qu'un fichier de configuration .babelrc . Le problème avec ceci est que, parce que nous utilisons create-react-app , nous ne pouvons pas configurer beaucoup de choses à moins d'éjecter. C'est là qu'interviennent les macros Babel.

Nous devrons installer babel-plugin-macros avec npm ou Yarn, puis créer un babel-plugin-macros.config.js à la racine de notre application, avec le contenu :

 module.exports = { styledComponents: { displayName: true, fileName: false, }, };

Avec la valeur fileName inversée, le displayName sera préfixé avec le nom du fichier pour une précision encore plus unique.

Nous devons également maintenant importer depuis la macro :

 // Before import styled from "styled-components"; // After import styled from "styled-components/macro";

Conclusion

Maintenant que vous pouvez composer votre CSS par programmation, n'abusez pas de la liberté. Pour ce que ça vaut, faites de votre mieux pour maintenir la raison dans vos composants stylés. N'essayez pas de composer des conditionnels lourds, ni de supposer que tout devrait être un composant stylé. De plus, ne faites pas trop d'abstraction en créant des composants de style naissant pour des cas d'utilisation dont vous ne faites que deviner qu'ils sont quelque part au coin de la rue.

Autres ressources

  1. Documentation, composants stylés
  2. "Construire un système de composants réutilisables avec React.js et des composants stylés", Lukas Gisder-Dube
  3. Utilisation avec Next.js
  4. Utilisation avec Gatsby