Traditionell habe ich beim Schreiben von Komponentenvarianten mit Tailwind CSS nach einfachen Klassenzuordnungen gegriffen, die Komponentenslots einen Prop-Wert zuordnen:
type TTheme = "DEFAULT" | "SECONDARY"; interface IComponentSlot { root: string; accent: string; } const THEME_MAP: Record<TTheme, IComponentSlot> = { DEFAULT: { root: "bg-red hover:background-pink", accent: "text-blue hover:text-green", }, SECONDARY: { root: "bg-green hover:background-black", accent: "text-pink hover:text-white", } } <div :class="THEME_MAP['DEFAULT'].root"> <div :class="THEME_MAP['DEFAULT'].accent">/**/</div> </div>
Das Problem bei diesem Ansatz besteht darin, die erforderlichen Klassen aufeinander abzustimmen und sicherzustellen, dass jede Variante über alle erforderlichen Klassen verfügt, insbesondere bei komplizierteren Komponenten. Bei Komponenten, bei denen wir einen Stil, beispielsweise eine Textfarbe, über verschiedene Komponentenslots hinweg teilen möchten, müssen wir jeden Slot einzeln aktualisieren.
Tailwind generiert Dienstprogrammklassen, indem es Ihre Codebasis scannt und Zeichenfolgen abgleicht. Das bedeutet, dass Tailwind zwar Klassen aus beliebigen Werten erstellen kann, wir sie jedoch nicht dynamisch initiieren können, ohne eine sichere Liste zu erstellen. Das wird also nicht funktionieren:
// .ts type TTheme = "DEFAULT" | "SECONDARY"; const colors: Record<TTheme, string> = { DEFAULT: "red", SECONDARY: "blue", } // .html <div :class="`text-[${colors[DEFAULT]}]`">
Wir können jedoch das gewünschte Verhalten nachahmen, indem wir CSS-Variablen nutzen, etwas, das Tailwind unter der Haube für viele seiner Klassen verwendet. Wir können eine Variable über eine Klasse in Tailwind mit der folgenden Syntax festlegen: [--my-variable-key:--my-variable-value]
Wie könnten wir also das obige Codebeispiel aktualisieren, um dynamische Werte zu verwenden?
// .ts type TTheme = "DEFAULT" | "SECONDARY"; const colors: Record<TTheme, string> = { DEFAULT: "[--text-color:red]", SECONDARY: "[--text-color:blue]", } // .html <div :class="[ colors[DEFAULT], 'text-[--text-color]' ]">
Jetzt verstehen wir die Einschränkungen von Tailwind und müssen nach Möglichkeiten suchen, unser ursprüngliches Problem zu lösen, das durch unseren Klassenkarten-Ansatz verursacht wurde. Wir können damit beginnen, unsere Klassenkarten zu vereinfachen:
type TTheme = "DEFAULT" | "SECONDARY"; interface IComponentSlot { root: string; accent: string; } const THEME_MAP: Record<TTheme, string> = { DEFAULT: "[--backgound:red] [--hover__background:pink] [--text:blue] [--hover__text:green]", SECONDARY: "[--backgound:green] [--hover__background:black] [--text:pink] [--hover__text:white]", } <div> <p>Unfortunately, this alone doesn't solve our problem, we still can't ensure we've set all of the classes we need to display each variant correctly. So how can we take this a step further? Well, we could begin writing an interface to force us to set specified values:<br> </p> <pre class="brush:php;toolbar:false">interface IComponentThemeVariables { backgound: string; hover__backgound: string; text: string; hover__text: string; } const THEME_MAP: Record<TTheme, IComponentThemeVariables> = { DEFAULT: { backgound: "[--backgound:red]", text: "[--hover__background:pink]", hover__background: "[--text:blue]", hover__text:"[--hover__text:green]", }, SECONDARY: { backgound: "[--backgound:green]", text: "[--hover__background:black]", hover__background: "[--text:pink]", hover__text:"[--hover__text:white]", }, }
Das würde also funktionieren, aber es gibt immer noch ein Problem: Nichts hindert uns daran, unsere String-Werte zu verwechseln. Beispielsweise könnten wir versehentlich den Schlüsselhintergrund auf [--text:blue] setzen.
Vielleicht sollten wir also auch unsere Werte eingeben. Wir können nicht ganze Klassen eingeben, das wäre ein Wartungsalptraum. Was wäre also, wenn wir unsere Farben eingeben und eine Hilfsmethode schreiben würden, um unsere CSS-Variablen zu generieren:
type TColor = "red" | "pink" | "blue" | "green" | "black" | "white"; interface IComponentThemeVariables { backgound: TColor; hover__backgound: TColor; text: TColor; hover__text: TColor; } // Example variableMap method at the end of the article const THEME_MAP: Record<TTheme, string> = { DEFAULT: variableMap({ backgound: "red", text: "pink", hover__background: "blue", hover__text:"green", }), SECONDARY: variableMap({ backgound: "green", text: "black", hover__background: "pink", hover__text:"white", }), }
Okay, das ist großartig. Wir können sicherstellen, dass wir immer die richtigen Variablen für jede Variante unserer Komponente festlegen. Aber warten Sie, wir sind gerade auf das erste Problem gestoßen, das wir mit Tailwind gefunden haben: Wir können nicht einfach Klassen generieren, Tailwind wird sie nicht aufgreifen. Wie werden wir das also angehen?
CSS in JS schien hier die offensichtliche Antwort zu sein: Generieren Sie einfach eine Klasse, die eine benutzerdefinierte Klasse mit den richtigen Variablen erstellt. Aber es gibt einen Haken: Das Javascript läuft auf dem Client und dies führt zu einem „Flash“, bei dem die Komponente zunächst geladen wird, ohne dass die Variablen festgelegt wurden, bevor sie aktualisiert wird, um korrekt angezeigt zu werden.
Bibliotheken wie Emotion lösen dieses Problem, indem sie Inline-Style-Tags zu den Komponenten einfügen:
<body> <div> <style data-emotion-css="21cs4">.css-21cs4 { font-size: 12 }</style> <div> <p>This didn't feel like the right approach to me.</p> <h3> So how do we solve this? </h3> <p>I was working with Vue, this led me down the path of v-bind in CSS, a feature in Vue to bind Javascript as CSS values. I'd only used this feature sparingly in the past and never taken a deep dive into what it's doing. v-bind in CSS simply sets an inline style on the relevant element.</p> <p>This jogged my memory about a Tweet I saw from the creator of Tailwind CSS, Adam Wathan a couple of months previously:</p> <p>So how does this help us? Well, while we can't dynamically generate Tailwind classes, we can dynamically generate inline styles and consume those inline styles from our Tailwind classes. So what would that look like?<br> </p> <pre class="brush:php;toolbar:false">type TColor = "red" | "pink" | "blue" | "green" | "black" | "white"; interface IComponentThemeVariables { backgound: TColor; hover__backgound: TColor; text: TColor; hover__text: TColor; } // Example variableMap method at the end of the article const THEME_MAP: Record<TTheme, string> = { DEFAULT: variableMap({ backgound: "red", text: "pink", hover__background: "blue", hover__text: "green", }), SECONDARY: variableMap({ backgound: "green", text: "black", hover__background: "pink", hover__text: "white", }), } <div > <h2> Conclusion </h2> <p>By combining the powers of Typescript, CSS variables, and inline styles we were able to ensure that while using Tailwind CSS, each variant of our component would have every option set and with the correct type.</p> <p>This is an experimental approach on which I'm sure there will be some strong opinions. Am I convinced this is the best approach? At this stage, I'm not sure, but I think it has legs.</p> <p>If you've found this article interesting or useful, please follow me on Bluesky (I'm most active here), Medium, Dev and/ or Twitter.</p> <h3> Example: variableMap </h3> <pre class="brush:php;toolbar:false">// variableMap example export const variableMap = <T extends Record<string, string>>( map: T ): string => { const styles: string[] = []; Object.entries(map).forEach(([key, value]) => { const wrappedValue = value.startsWith("--") ? `var(${value})` : value; const variableClass = `--${key}: ${wrappedValue};`; styles.push(variableClass); }); return styles.join(" "); };
Das obige ist der detaillierte Inhalt vonEin anderer Ansatz zum Schreiben von Komponentenvarianten mit Tailwind CSS. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!