Maison > interface Web > tutoriel CSS > Tout n'a pas besoin d'un composant

Tout n'a pas besoin d'un composant

DDD
Libérer: 2024-11-26 03:32:12
original
221 Les gens l'ont consulté

Not Everything Needs a Component

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.

Composants

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>
    );
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

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> 
Copier après la connexion
Copier après la connexion

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>
    );
}
Copier après la connexion
Copier après la connexion

Colocalisation d'un

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>
    );
}
Copier après la connexion
Copier après la connexion

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.

Le coût (évitable) de la flexibilité

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>
    );
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

À 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.

Retour aux sources

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> 
Copier après la connexion
Copier après la connexion

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>
    );
}
Copier après la connexion
Copier après la connexion

Ajouter la sécurité des types dans les frameworks JavaScript

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>
    );
}
Copier après la connexion
Copier après la connexion

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>
    );
}
Copier après la connexion

Empêcher la duplication de code et les valeurs codées en dur

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);
}
Copier après la connexion
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>  
*/
Copier après la connexion

Alternatives

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>
    );
}
Copier après la connexion
Copier après la connexion
Copier après la connexion

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 ?

Points à retenir

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!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal