Anfang der 2000er Jahre wurde ein neuer Begriff, Divitis, geprägt, der sich auf Die Praxis des Verfassens von Webseitencode mit vielen div-Elementen bezieht Ort sinnvoller semantischer HTML-Elemente
. Dies war Teil der Bemühungen, das Bewusstsein für die Semantik in HTML im Rahmen der Progressive Enhancement-Technik zu schärfen.
20 Jahre später bin ich Zeuge eines neuen Syndroms, das Webentwickler betrifft und das ich Komponentitis nenne. Hier ist meine erfundene Definition:
Componentitis: die Praxis, eine Komponente für jeden Aspekt einer Benutzeroberfläche anstelle einer einfacheren und wiederverwendbareren Lösung zu erstellen.
Also zunächst einmal: Was ist eine Komponente? Ich denke, React hat den Begriff populär gemacht, um sich auf seine Bausteine zu beziehen:
Mit React können Sie Ihr Markup, CSS und JavaScript zu benutzerdefinierten „Komponenten“ kombinieren, wiederverwendbaren UI-Elementen für Ihre App.
— React-Dokumentation – Ihre erste Komponente
Während das Konzept der wiederverwendbaren UI-Elemente damals nicht neu war (in CSS gab es bereits Techniken wie OOCSS, SMACSS und BEM), liegt der Hauptunterschied in der ursprünglichen Herangehensweise an die Position von Markup, Stil und Interaktion. Mit React-Komponenten (und allen nachfolgenden UI-Bibliotheken) ist es möglich, alles in einer einzigen Datei innerhalb der Grenzen einer Komponente zusammenzulokalisieren.
Mit der neuesten CSS-Bibliothek Stylex von Facebook könnten Sie also schreiben:
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> ); }
Sie können ein Fan davon sein oder nicht, CSS in Objektnotation zu schreiben (ich bin es nicht), aber diese Ebene der gemeinsamen Anordnung ist oft eine gute Möglichkeit, ein komponentenbasiertes Projekt wartbarer zu machen: Alles ist in Reichweite und ausdrücklich gebunden.
In Bibliotheken wie Svelte ist die Co-Location noch klarer (und der Code prägnanter):
<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>
Im Laufe der Zeit hat dieses Muster so viel Anklang gefunden, dass alles in Komponenten gekapselt ist. Sie sind wahrscheinlich auf Seitenkomponenten wie diese gestoßen:
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> ); }
Der obige Code sieht sauber und konsistent aus: Wir verwenden die Komponentenschnittstelle, um eine Seite zu beschreiben.
Aber dann schauen wir uns die mögliche Implementierung von Stack an. Diese Komponente ist normalerweise ein Wrapper, um sicherzustellen, dass alle direkten untergeordneten Elemente vertikal gestapelt und gleichmäßig verteilt sind:
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> ); }
Wir definieren nur die Stile und das Stammelement der Komponente.
In diesem Fall könnten wir sogar sagen, dass das Einzige, was wir gemeinsam platzieren, der Stilblock ist, da der HTML-Code nur zum Speichern einer CSS-Klassenreferenz verwendet wird und es keine Interaktivität oder Geschäftlichkeit gibt Logik.
Was ist nun, wenn wir das Stammelement als Abschnitt rendern und möglicherweise einige Attribute hinzufügen möchten? Wir müssen in den Bereich der polymorphen Komponenten vordringen. In React und mit TypeScript könnte dies am Ende etwa so aussehen:
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> ); }
Meiner Meinung nach ist das auf den ersten Blick nicht sehr lesbar. Und denken Sie daran: Wir rendern lediglich ein Element mit 3 CSS-Deklarationen.
Vor einiger Zeit arbeitete ich an einem Lieblingsprojekt in Angular. Da ich es gewohnt war, in Komponenten zu denken, wandte ich mich an sie, um einen Stack zu erstellen. Es stellt sich heraus, dass die Erstellung polymorpher Komponenten in Angular noch komplexer ist.
Ich fing an, meinen Implementierungsentwurf in Frage zu stellen, und dann hatte ich eine Offenbarung: Warum sollte ich Zeit und Codezeilen für komplexe Implementierungen aufwenden, wenn die Lösung die ganze Zeit direkt vor mir lag?
<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>
Wirklich, das ist die grundlegende native Implementierung des Stacks. Sobald Sie das CSS im Layout geladen haben, kann es sofort in Ihrem Code verwendet werden:
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> ); }
Die reine CSS-Lösung bietet weder Eingabe noch IDE-Autovervollständigung.
Wenn wir außerdem keine Abstandsvarianten verwenden, könnte es sich zu ausführlich anfühlen, sowohl ein Klassen- als auch ein Stilattribut anstelle einer Abstandsstütze zu schreiben. Vorausgesetzt, Sie verwenden React, könnten Sie JSX nutzen und eine Hilfsfunktion erstellen:
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> ); }
Beachten Sie, dass React TypeScript keine unbekannten CSS-Eigenschaften zulässt. Der Kürze halber habe ich eine Typzusicherung verwendet, aber Sie sollten eine robustere Lösung wählen.
Wenn Sie Varianten verwenden, können Sie die Dienstprogrammfunktion ändern, um eine Entwicklererfahrung ähnlich den PandaCSS-Mustern bereitzustellen:
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> ); }
Einige von Ihnen haben vielleicht bemerkt, dass ich im letzten Beispiel die erwarteten Abstandswerte sowohl im CSS als auch in den Dienstprogrammdateien fest codiert habe. Wenn ein Wert entfernt oder hinzugefügt wird, kann dies ein Problem darstellen, da wir die beiden Dateien synchron halten müssen.
Wenn Sie eine Bibliothek erstellen, werden automatisierte visuelle Regressionstests diese Art von Problem wahrscheinlich erkennen. Wie auch immer, wenn es Sie immer noch stört, könnte eine Lösung darin bestehen, nach CSS-Modulen zu greifen und entweder typisierte CSS-Module zu verwenden oder einen Laufzeitfehler für nicht unterstützte Werte auszulösen:
<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> */
Wenn Sie immer noch der Meinung sind, dass eine polymorphe Komponente besser wäre, Sie wirklich nicht mit einfachem HTML umgehen können oder CSS nicht in eine separate Datei schreiben möchten (obwohl ich nicht sicher bin, warum), wäre mein nächster Vorschlag, dies zu tun Werfen Sie einen Blick auf PandaCSS und erstellen Sie benutzerdefinierte Muster oder erkunden Sie andere Optionen wie Vanilla-Extract. Meiner Meinung nach sind diese Tools eine überentwickelte CSS-Metasprache, aber immer noch besser als eine polymorphe Komponente.
Eine weitere erwägenswerte Alternative ist Tailwind CSS, das den Vorteil hat, dass es zwischen Sprachen und Frameworks interoperabel ist.
Unter Verwendung der von Tailwind definierten Standardabstandsskala könnten wir ein Stack-Plugin wie dieses erstellen:
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> ); }
Als Randbemerkung: Es ist interessant, dass Tailwind das mentale Komponentenmodell in matchComponents verwendet, um komplexe CSS-Regelsätze zu beschreiben, auch wenn dadurch keine echte Komponente erstellt wird. Vielleicht ein weiteres Beispiel dafür, wie allgegenwärtig das Konzept ist?
Der Fall der Componentitis zeigt über die technischen Aspekte hinaus, wie wichtig es ist, innezuhalten, um unsere mentalen Modelle und Gewohnheiten zu untersuchen und zu hinterfragen. Wie viele Muster in der Softwareentwicklung entstanden Komponenten als Lösungen für reale Probleme, aber als wir begannen, standardmäßig auf dieses Muster zurückzugreifen, wurde es zu einer stillen Quelle der Komplexität. Komponentitis ähnelt den Nährstoffmängeln, die durch eine eingeschränkte Ernährung verursacht werden: Das Problem liegt nicht an einem einzelnen Lebensmittel, sondern darin, dass alles andere fehlt.
Das obige ist der detaillierte Inhalt vonNicht alles braucht eine Komponente. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!