Au début des années 2000, un nouveau terme, Divitis, a été inventé pour désigner la pratique consistant à créer du code de page Web avec de nombreux éléments div dans place des éléments HTML sémantiques significatifs
. Cela faisait partie d'un effort visant à accroître la sensibilisation à la sémantique en HTML dans le cadre de la technique d'amélioration progressive.
Avance rapide de 20 ans : je suis témoin d'un nouveau syndrome affectant les développeurs Web, que j'appelle la componentite. Voici ma définition inventée :
Componentite : la pratique consistant à créer un composant pour chaque aspect d'une interface utilisateur à la place d'une solution plus simple et plus réutilisable.
Alors, tout d'abord, qu'est-ce qu'un composant ? Je pense que React a popularisé le terme pour désigner ses éléments constitutifs :
React vous permet de combiner votre balisage, CSS et JavaScript dans des « composants » personnalisés, éléments d'interface utilisateur réutilisables pour votre application.
— Documentation React - Votre premier composant
Bien que le concept d'éléments d'interface utilisateur réutilisables n'était pas nouveau à l'époque (en CSS, nous avions déjà des techniques comme OOCSS, SMACSS et BEM), la principale différence réside dans son approche originale de l'emplacement du balisage, du style et interaction. Avec les composants React (et toutes les bibliothèques d'interface utilisateur ultérieures), il est possible de tout colocaliser dans un seul fichier dans les limites d'un composant.
Ainsi, en utilisant la dernière bibliothèque CSS Stylex de Facebook, vous pourriez écrire :
import * as stylex from "@stylexjs/stylex"; import { useState } from "react"; // styles const styles = stylex.create({ base: { fontSize: 16, lineHeight: 1.5, color: "#000", }, }); export function Toggle() { // interactions const [toggle, setToggle] = useState(false); const onClick = () => setToggle((t) => !t); // markup return ( <button {...stylex.props(styles.base)} type="button" onClick={onClick}> {toggle} </button> ); }
Vous pouvez être fan ou non de l'écriture CSS en notation objet (je ne le suis pas), mais ce niveau de colocalisation est souvent un bon moyen de rendre un projet basé sur des composants plus maintenable : tout est à portée de main et explicitement lié.
Dans les bibliothèques comme Svelte, la colocalisation est encore plus claire (et le code plus concis) :
<script> let toggle = $state(false) const onclick = () => toggle = !toggle </script> <button type='button' {onclick}> {toggle} </button> <style> button { font-size: 16px; line-height: 1.5; color: #000; } </style>
Au fil du temps, ce modèle a gagné en popularité au point que tout est encapsulé dans des composants. Vous avez probablement rencontré des composants de page comme celui-ci :
export function Page() { return ( <Layout> <Header nav={<Nav />} /> <Body> <Stack spacing={2}> <Item>Item 1</Item> <Item>Item 2</Item> <Item>Item 3</Item> </Stack> </Body> <Footer /> </Layout> ); }
Le code ci-dessus semble propre et cohérent : nous utilisons l'interface du composant pour décrire une page.
Mais ensuite, regardons la possible implémentation de Stack. Ce composant est généralement un wrapper pour garantir que tous les éléments enfants directs sont empilés verticalement et uniformément espacés :
import * as stylex from "@stylexjs/stylex"; import type { PropsWithChildren } from "react"; const styles = stylex.create({ root: { display: "flex", flexDirection: "column", }, spacing: (value) => ({ rowGap: value * 16, }), }); export function Stack({ spacing = 0, children, }: PropsWithChildren<{ spacing?: number }>) { return ( <div {...stylex.props(styles.root, styles.spacing(spacing))}> {children} </div> ); }
Nous définissons uniquement les styles et l'élément racine du composant.
Dans ce cas, on pourrait même dire que la seule chose que nous colocalisons est le bloc de style puisque le HTML ne sert qu'à contenir une référence de classe CSS, et il n'y a pas d'interactivité ni de business logique.
Maintenant, que se passe-t-il si nous voulons pouvoir restituer l'élément racine sous forme de section et peut-être ajouter quelques attributs ? Nous devons entrer dans le domaine des composants polymorphes. Dans React et avec TypeScript, cela pourrait ressembler à ceci :
import * as stylex from "@stylexjs/stylex"; import { useState } from "react"; // styles const styles = stylex.create({ base: { fontSize: 16, lineHeight: 1.5, color: "#000", }, }); export function Toggle() { // interactions const [toggle, setToggle] = useState(false); const onClick = () => setToggle((t) => !t); // markup return ( <button {...stylex.props(styles.base)} type="button" onClick={onClick}> {toggle} </button> ); }
À mon avis, ce n'est pas très lisible au premier coup d'œil. Et rappelez-vous : nous rendons simplement un élément avec 3 déclarations CSS.
Il y a quelque temps, je travaillais sur un projet animalier en Angular. Étant habitué à penser en composants, je les ai contactés pour créer une Stack. Il s’avère que dans Angular les composants polymorphes sont encore plus complexes à créer.
J'ai commencé à remettre en question la conception de ma mise en œuvre, puis j'ai eu une révélation : pourquoi consacrer du temps et des lignes de code à des implémentations complexes alors que la solution avait toujours été devant moi ?
<script> let toggle = $state(false) const onclick = () => toggle = !toggle </script> <button type='button' {onclick}> {toggle} </button> <style> button { font-size: 16px; line-height: 1.5; color: #000; } </style>
Vraiment, c’est l’implémentation simple native de Stack . Une fois que vous chargez le CSS dans la mise en page, il peut être utilisé immédiatement dans votre code :
export function Page() { return ( <Layout> <Header nav={<Nav />} /> <Body> <Stack spacing={2}> <Item>Item 1</Item> <Item>Item 2</Item> <Item>Item 3</Item> </Stack> </Body> <Footer /> </Layout> ); }
La solution CSS uniquement ne fournit ni saisie ni saisie semi-automatique IDE.
De plus, si nous n'utilisons pas de variantes d'espacement, cela peut sembler trop verbeux d'écrire à la fois un attribut de classe et un attribut de style au lieu d'un accessoire d'espacement. En supposant que vous utilisez React, vous pouvez exploiter JSX et créer une fonction utilitaire :
import * as stylex from "@stylexjs/stylex"; import type { PropsWithChildren } from "react"; const styles = stylex.create({ root: { display: "flex", flexDirection: "column", }, spacing: (value) => ({ rowGap: value * 16, }), }); export function Stack({ spacing = 0, children, }: PropsWithChildren<{ spacing?: number }>) { return ( <div {...stylex.props(styles.root, styles.spacing(spacing))}> {children} </div> ); }
Notez que React TypeScript n'autorise pas les propriétés CSS inconnues. J'ai utilisé une assertion de type par souci de concision, mais vous devriez choisir une solution plus robuste.
Si vous utilisez des variantes, vous pouvez modifier la fonction utilitaire pour offrir une expérience de développement similaire aux modèles PandaCSS :
import * as stylex from "@stylexjs/stylex"; type PolymorphicComponentProps<T extends React.ElementType> = { as?: T; children?: React.ReactNode; spacing?: number; } & React.ComponentPropsWithoutRef<T>; const styles = stylex.create({ root: { display: "flex", flexDirection: "column", }, spacing: (value) => ({ rowGap: value * 16, }), }); export function Stack<T extends React.ElementType = "div">({ as, spacing = 1, children, ...props }: PolymorphicComponentProps<T>) { const Component = as || "div"; return ( <Component {...props} {...stylex.props(styles.root, styles.spacing(spacing))} > {children} </Component> ); }
Certains d'entre vous ont peut-être remarqué que, dans le dernier exemple, j'ai codé en dur les valeurs d'espacement attendues dans les fichiers CSS et utilitaires. Si une valeur est supprimée ou ajoutée, cela peut poser un problème car nous devons garder les deux fichiers synchronisés.
Si vous créez une bibliothèque, les tests de régression visuelle automatisés détecteront probablement ce genre de problème. Quoi qu'il en soit, si cela vous dérange toujours, une solution pourrait être d'accéder aux modules CSS et d'utiliser des modules typed-css ou de générer une erreur d'exécution pour les valeurs non prises en charge :
<div> <pre class="brush:php;toolbar:false">.stack { --s: 0; display: flex; flex-direction: column; row-gap: calc(var(--s) * 16px); }
export function Page() { return ( <Layout> <Header nav={<Nav />} /> <Body> <div className="stack"> <p>Let's see the main advantages of this approach:</p> <ul> <li>reusability</li> <li>reduced complexity</li> <li>smaller JavaScript bundle and less overhead</li> <li><strong>interoperability</strong></li> </ul> <p>The last point is easy to overlook: Not every project uses React, and if you’re including the stack layout pattern in a Design System or a redistributable UI library, developers could use it in projects using different UI frameworks or a server-side language like PHP or Ruby.</p> <h2> Nice features and improvements </h2> <p>From this base, you can iterate to add more features and improve the developer experience. While some of the following examples target React specifically, they can be easily adapted to other frameworks.</p> <h3> Control spacing </h3> <p>If you're developing a component library you definitely want to define a set of pre-defined spacing variants to make space more consistent. This approach also eliminates the need to explicitly write the style attribute:<br> </p> <pre class="brush:php;toolbar:false">.stack { --s: 0; display: flex; flex-direction: column; row-gap: calc(var(--s) * 16px); &.s\:1 { --s: 1 } &.s\:2 { --s: 2 } &.s\:4 { --s: 4 } &.s\:6 { --s: 6 } } /** Usage: <div> <p>For a bolder approach to spacing, see Complementary Space by Donnie D'Amato.</p> <h3> Add better scoping </h3> <p>Scoping, in this case, refers to techniques to prevent conflicts with other styles using the same selector. I’d argue that scoping issues affects a pretty small number of projects, but if you are really concerned about it, you could:</p> <ol> <li>Use something as simple as CSS Modules, which is well supported in all major bundlers and frontend frameworks.</li> <li>Use cascade layers resets to prevent external stylesheets from modifying your styles (this is an interesting technique).</li> <li>Define a specific namespace like .my-app-... for your classes.</li> </ol> <p>Here is the result with CSS Modules:<br> </p> <pre class="brush:php;toolbar:false">.stack { --s: 0; display: flex; flex-direction: column; row-gap: calc(var(--s) * 16px); &.s1 { --s: 1 } &.s2 { --s: 2 } &.s4 { --s: 4 } &.s6 { --s: 6 } } /** Usage import * from './styles/stack.module.css' <div className={`${styles.stack} ${styles.s2}`}> // ... </div> */
Si vous pensez toujours qu'un composant polymorphe serait meilleur, que vous ne pouvez vraiment pas gérer le HTML simple ou que vous ne voulez pas écrire du CSS dans un fichier séparé (même si je ne sais pas pourquoi), ma prochaine suggestion serait de jetez un œil à PandaCSS et créez des modèles personnalisés ou explorez d'autres options comme l'extrait de vanille. À mon avis, ces outils sont un métalangage CSS sur-conçu mais toujours meilleur qu'un composant polymorphe.
Une autre alternative à considérer est Tailwind CSS, qui présente l'avantage d'être interopérable entre les langages et les frameworks.
En utilisant l'échelle d'espacement par défaut définie par Tailwind, nous pourrions créer un plugin de pile comme celui-ci :
import * as stylex from "@stylexjs/stylex"; import { useState } from "react"; // styles const styles = stylex.create({ base: { fontSize: 16, lineHeight: 1.5, color: "#000", }, }); export function Toggle() { // interactions const [toggle, setToggle] = useState(false); const onClick = () => setToggle((t) => !t); // markup return ( <button {...stylex.props(styles.base)} type="button" onClick={onClick}> {toggle} </button> ); }
En remarque : il est intéressant que Tailwind utilise le modèle mental de composant dans matchComponents pour décrire des ensembles de règles CSS complexes, même s'il ne crée aucun composant réel. Peut-être un autre exemple de l’omniprésence du concept ?
Le cas de la Componentitis, au-delà de ses aspects techniques, démontre l'importance de s'arrêter pour examiner et remettre en question nos modèles et habitudes mentaux. Comme de nombreux modèles de développement logiciel, les composants sont apparus comme des solutions à des problèmes réels, mais lorsque nous avons commencé à adopter ce modèle par défaut, il est devenu une source silencieuse de complexité. La Componentite ressemble à ces carences nutritionnelles causées par un régime alimentaire restreint : le problème ne vient pas d'un seul aliment mais plutôt du manque de tout le reste.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!