Typer

Utökning av webbläsarens inbyggda HTML-element för maximal anpassning.

När du bygger återanvändbara komponenter är korrekt typning avgörande för att skapa flexibla, anpassningsbara och typesäkra gränssnitt. Genom att följa etablerade mönster för komponenttyper kan du säkerställa att dina komponenter både är kraftfulla och lätta att använda.

Omslutning av ett enda element

Varje exporterad komponent bör i idealfallet omsluta ett enda HTML- eller JSX-element. Denna princip är grundläggande för att skapa komponerbara, anpassningsbara komponenter.

När en komponent omsluter flera element blir det svårt att anpassa specifika delar utan prop drilling eller komplexa API:er. Tänk på följande antimönster:

@/components/ui/card.tsx
const Card = ({ title, description, footer, ...props }) => (
  <div {...props}>
    <div className="card-header">
      <h2>{title}</h2>
      <p>{description}</p>
    </div>
    <div className="card-footer">
      {footer}
    </div>
  </div>
);

Som vi diskuterade i Komposition, skapar detta tillvägagångssätt flera problem:

  • Du kan inte anpassa header-styling utan att lägga till fler props
  • Du kan inte styra vilka HTML-element som används för titel och beskrivning
  • Du tvingas in i en specifik DOM-struktur

Istället bör varje lager vara en egen komponent. Detta gör att du kan anpassa varje lager oberoende och kontrollera de exakta HTML-elementen som används för titel och beskrivning.

Fördelarna med detta tillvägagångssätt är:

  • Full anpassning - Användare kan styla och ändra varje lager oberoende
  • Ingen prop drilling - Props skickas direkt till det element som behöver dem
  • Semantisk HTML - Användare kan se och kontrollera den exakta DOM-strukturen
  • Bättre tillgänglighet - Direkt kontroll över ARIA-attribut och semantiska element
  • Enklare mental modell - En komponent = ett element

Utökning av HTML-attribut

Varje komponent bör utvidga de inbyggda HTML-attributen för det element den omsluter. Detta säkerställer att användare har full kontroll över det underliggande HTML-elementet.

Grundläggande mönster

export type CardRootProps = React.ComponentProps<'div'> & {
  // Add your custom props here
  variant?: 'default' | 'outlined';
};

export const CardRoot = ({ variant = 'default', ...props }: CardRootProps) => (
  <div {...props} />
);

Vanliga typer för HTML-attribut

React tillhandahåller typdefinitioner för alla HTML-element. Använd den som passar för din komponent:

// For div elements
type DivProps = React.ComponentProps<'div'>;

// For button elements
type ButtonProps = React.ComponentProps<'button'>;

// For input elements
type InputProps = React.ComponentProps<'input'>;

// For form elements
type FormProps = React.ComponentProps<'form'>;

// For anchor elements
type LinkProps = React.ComponentProps<'a'>;

Hantera olika elementtyper

När en komponent kan renderas som olika element, använd generics eller unionstyper:

// Using discriminated unions
export type ButtonProps =
  | (React.ComponentProps<'button'> & { asChild?: false })
  | (React.ComponentProps<'div'> & { asChild: true });

// Or with a polymorphic approach
export type PolymorphicProps<T extends React.ElementType> = {
  as?: T;
} & React.ComponentPropsWithoutRef<T>;

Utöka anpassade komponenter

Om du utökar en befintlig komponent kan du använda typen ComponentProps för att hämta komponentens props.

@/components/ui/share-button.tsx
import type { ComponentProps } from 'react';

export type ShareButtonProps = ComponentProps<'button'>;

export const ShareButton = (props: ShareButtonProps) => (
  <button {...props} />
);

Exportera typer

Exportera alltid dina komponentprops-typer. Detta gör dem tillgängliga för konsumenter i olika användningsfall.

Export av typer möjliggör flera viktiga mönster:

// 1. Extracting specific prop types
import type { CardRootProps } from '@/components/ui/card';
type variant = CardRootProps['variant'];

// 2. Extending components
export type ExtendedCardProps = CardRootProps & {
  isLoading?: boolean;
};

// 3. Creating wrapper components
const MyCard = (props: CardRootProps) => (
  <CardRoot {...props} className={cn('my-custom-class', props.className)} />
);

// 4. Type-safe prop forwarding
function useCardProps(): Partial<CardRootProps> {
  return {
    variant: 'outlined',
    className: 'custom-card',
  };
}

Dina exporterade typer bör namnges <ComponentName>Props. Detta är en konvention som hjälper andra utvecklare att förstå typens syfte.

Bästa praxis

1. Sprid alltid props sist

Säkerställ att användare kan skriva över eventuella standardprops:

// ✅ Good - user props override defaults
<div className="default-class" {...props} />

// ❌ Bad - defaults override user props
<div {...props} className="default-class" />

2. Undvik namnkonflikter för props

Använd inte prop-namn som konflikterar med HTML-attribut om du inte medvetet vill åsidosätta dem:

// ❌ Bad - conflicts with HTML title attribute
export type CardProps = React.ComponentProps<'div'> & {
  title: string; // This conflicts with the HTML title attribute
};

// ✅ Good - use a different name
export type CardProps = React.ComponentProps<'div'> & {
  heading: string;
};

3. Dokumentera anpassade props

Lägg till JSDoc-kommentarer för anpassade props för en bättre utvecklarupplevelse:

export type DialogProps = React.ComponentProps<'div'> & {
  /** Whether the dialog is currently open */
  open: boolean;
  /** Callback when the dialog requests to be closed */
  onOpenChange: (open: boolean) => void;
  /** Whether to render the dialog in a portal */
  modal?: boolean;
};