Tyylittely
Ehdollinen ja yhdisteltävä tyylitys Tailwind-luokkien avulla.
Nykyaikaiset komponenttikirjastot tarvitsevat joustavia tyylijärjestelmiä, jotka pystyvät käsittelemään monimutkaisia vaatimuksia tinkimättä kehittäjäkokemuksesta. Tailwind CSS:n yhdistäminen älykkääseen luokkien yhdistelyyn on muodostunut tehokkaaksi malliksi räätälöitävien komponenttien rakentamiseen.
Tämä lähestymistapa ratkaisee perustavanlaatuisen jännitteen järkevistä oletusarvoista ja täydellisestä mukautettavuudesta - haaste, joka on vaivannut komponenttikirjastoja vuosia.
Ongelma perinteisen tyylityksen kanssa
Perinteiset CSS-lähestymistavat johtavat usein spesifisyyssotiin, tyylikonflikteihin ja ennakoimattomiin ylikirjoituksiin. Kun välität komponentille className="bg-blue-500", mutta komponentissa on jo bg-red-500, kumpi voittaa?
Ilman asianmukaista käsittelyä molemmat luokat voivat päteä ja lopputulos riippuu monista tekijöistä – CSS:n lähdejärjestyksestä, luokkien spesifisyydestä, bundlerin luokkien yhdistelyalgoritmista jne.
Luokkien älykäs yhdistäminen
tailwind-merge-kirjasto ratkaisee tämän ymmärtämällä Tailwindin luokkarakenteen ja ratkaisemalla konfliktit älykkäästi. Kun kaksi luokkaa kohdistuvat samaan CSS-ominaisuuteen, se pitää vain viimeisen.
// Both bg-red-500 and bg-blue-500 apply - unpredictable result
<Button className="bg-blue-500" />
// Renders: className="bg-red-500 bg-blue-500"import { twMerge } from 'tailwind-merge';
// bg-blue-500 wins as it comes last
const className = twMerge('bg-red-500', 'bg-blue-500');
// Returns: "bg-blue-500"Tämä toimii kaikkien Tailwind-apuohjelmien kanssa:
twMerge('px-4 py-2', 'px-8'); // Returns: "py-2 px-8"
twMerge('text-sm', 'text-lg'); // Returns: "text-lg"
twMerge('hover:bg-red-500', 'hover:bg-blue-500'); // Returns: "hover:bg-blue-500"Kirjasto ymmärtää myös Tailwindin modifikaattorijärjestelmän:
// Modifiers are handled correctly
twMerge('hover:bg-red-500 focus:bg-red-500', 'hover:bg-blue-500');
// Returns: "focus:bg-red-500 hover:bg-blue-500"Ehdolliset luokat
Usein tarvitsee soveltaa luokkia ehdollisesti propsien tai tilan perusteella. clsx-kirjasto tarjoaa selkeän rajapinnan tähän:
import clsx from 'clsx';
// Basic conditionals
clsx('base', isActive && 'active');
// Returns: "base active" (if isActive is true)
// Object syntax
clsx('base', {
'active': isActive,
'disabled': isDisabled,
});
// Arrays
clsx(['base', isLarge ? 'text-lg' : 'text-sm']);
// Mixed
clsx(
'base',
['array-item'],
{ 'object-conditional': true },
isActive && 'conditional'
);Yleinen käytäntö on yhdistää oletusarvoinen luokkajoukko saapuviin props-arvoihin sekä mahdolliseen omaan logiikkaamme:
const Component = ({ className, ...props }: ComponentProps) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div
className={cn(
"rounded-lg border bg-white shadow-sm",
isOpen && "bg-blue-500",
className
)}
{...props}
/>
);
};cn-apuohjelma
cn-funktio, jonka tunnetuksi teki shadcn/ui, yhdistää clsx- ja tailwind-merge-kirjastot tarjoten sekä ehdollisen logiikan että älykkään yhdistelyn:
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}Teho syntyy järjestyksestä - perusmuotoilu ensin, ehdolliset tyylit toisena, käyttäjän ylikirjoitukset viimeisenä. Tämä varmistaa ennakoitavan käyttäytymisen samalla kun säilytetään täysi mukautettavuus.
Class Variance Authority (CVA)
Monimutkaisille komponenteille, joilla on paljon variantteja, ehdollisten luokkien manuaalinen hallinta käy nopeasti työlääksi. Class Variance Authority (CVA) tarjoaa deklaratiivisen rajapinnan komponenttivarianttien määrittelyyn.
Esimerkiksi tässä on ote shadcn/ui:n Button-komponentista:
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)Parhaat käytännöt
1. Järjestyksellä on merkitystä
Sovella luokkia aina tässä järjestyksessä:
- Perusmuotoilut (aina käytössä)
- Varianttien tyylit (propsien perusteella)
- Ehdolliset tyylit (tilan perusteella)
- Käyttäjän ylikirjoitukset (className-prop)
className={cn(
'base-styles', // 1. Base
variant && variantStyles, // 2. Variants
isActive && 'active', // 3. Conditionals
className // 4. User overrides
)}2. Dokumentoi varianttisi
Käytä TypeScriptiä ja JSDocia kuvaamaan, mitä kukin variantti tekee:
type ButtonProps = {
/**
* The visual style of the button
* @default "primary"
*/
variant?: 'primary' | 'secondary' | 'destructive' | 'ghost';
/**
* The size of the button
* @default "md"
*/
size?: 'sm' | 'md' | 'lg';
};3. Ota toistuvat mallit talteen
Jos huomaat kirjoittavasi samaa ehdollista logiikkaa toistuvasti, erottele se:
export const focusRing = 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500';
export const disabled = 'disabled:pointer-events-none disabled:opacity-50';
// Use in components
className={cn(focusRing, disabled, className)}Migraatio-opas
Jos siirryt eri tyylityslähestymistavasta, tässä on miten sopeuttaa yleisiä malleja:
CSS Modulesista
import styles from './Button.module.css';
<button className={`${styles.button} ${styles[variant]} ${className}`} />import { cn } from '@/lib/utils';
<button className={cn(
'px-4 py-2 rounded-lg',
variant === 'primary' && 'bg-blue-500 text-white',
className
)} />styled-componentsista
const Button = styled.button<{ $primary?: boolean }>`
padding: 8px 16px;
background: ${props => props.$primary ? 'blue' : 'gray'};
`;function Button({ primary, className, ...props }) {
return (
<button
className={cn(
'px-4 py-2',
primary ? 'bg-blue-500' : 'bg-gray-500',
className
)}
{...props}
/>
);
}Suorituskykylähtökohdat
Sekä clsx että tailwind-merge ovat erittäin optimoituja, mutta pidä nämä vinkit mielessä:
-
Määrittele variantit komponentin ulkopuolella - CVA-variantit tulisi määritellä komponentin ulkopuolelle, jotta niitä ei luoda uudelleen jokaisella renderöinnillä.
-
Muistimuistioi (memoize) monimutkaiset laskelmat - Jos sinulla on raskasta ehdollista logiikkaa, harkitse muistimuistion käyttöä:
const className = useMemo(
() => cn(
baseStyles,
expensiveComputation(props),
className
),
[props, className]
);- Käytä CSS-muuttujia dynaamisille arvoille - Dynaamisten arvojen luokkien generoimisen sijaan käytä CSS-muuttujia:
// Good
<div
className="bg-[var(--color)]"
style={{ '--color': dynamicColor } as React.CSSProperties}
/>
// Avoid
<div className={`bg-[${dynamicColor}]`} />Tailwind CSS:n, älykkään luokkien yhdistelyn ja variantti-rajapintojen yhdistelmä tarjoaa vankan perustan komponenttien tyylittämiselle. Tämä lähestymistapa skaalautuu yksinkertaisista painikkeista monimutkaisiin design-järjestelmiin säilyttäen ennakoitavuuden ja hyvän kehittäjäkokemuksen.