Typer

Utvider nettleserens innebygde HTML-elementer for maksimal tilpasning.

Når du bygger gjenbrukbare komponenter, er riktig typing avgjørende for å lage fleksible, tilpassbare og typesikre grensesnitt. Ved å følge etablerte mønstre for komponenttyper kan du sikre at komponentene dine både er kraftige og enkle å bruke.

Innpakking av enkelt element

Hver eksporterte komponent bør ideelt sett pakke inn ett enkelt HTML- eller JSX-element. Dette prinsippet er grunnleggende for å lage komponerbare, tilpassbare komponenter.

Når en komponent pakker inn flere elementer, blir det vanskelig å tilpasse spesifikke deler uten prop-drilling eller komplekse API-er. Vurder dette anti-mønsteret:

@/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 diskuterte i Komposisjon, skaper denne tilnærmingen flere problemer:

  • Du kan ikke tilpasse header-stilen uten å legge til flere props
  • Du kan ikke kontrollere hvilke HTML-elementer som brukes for tittel og beskrivelse
  • Du blir tvunget inn i en spesifikk DOM-struktur

I stedet bør hvert lag være sin egen komponent. Dette lar deg tilpasse hvert lag uavhengig, og kontrollere de eksakte HTML-elementene som brukes for tittel og beskrivelse.

Fordelene med denne tilnærmingen er:

  • Maksimal tilpasning - Brukere kan style og endre hvert lag uavhengig
  • Ingen prop-drilling - Props går direkte til elementet som trenger dem
  • Semantisk HTML - Brukere kan se og kontrollere den eksakte DOM-strukturen
  • Bedre tilgjengelighet - Direkte kontroll over ARIA-attributter og semantiske elementer
  • Enklere mental modell - Én komponent = ett element

Utvide HTML-attributter

Hver komponent bør utvide de native HTML-attributtene til elementet den omslutter. Dette sikrer at brukere har full kontroll over det underliggende HTML-elementet.

Grunnleggende 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} />
);

Vanlige HTML-attributt-typer

React gir typedefinisjoner for alle HTML-elementer. Bruk den passende for 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'>;

Håndtering av ulike elementtyper

Når en komponent kan rendere som forskjellige elementer, bruk generics eller union-typer:

// 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>;

Utvide egendefinerte komponenter

Hvis du utvider en eksisterende komponent, kan du bruke typen ComponentProps for å få propsene til komponenten.

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

export type ShareButtonProps = ComponentProps<'button'>;

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

Eksportere typer

Eksporter alltid komponentens proptypene. Dette gjør dem tilgjengelige for forbrukere i ulike bruksområder.

Å eksportere typer muliggjør flere viktige mønstre:

// 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',
  };
}

Dine eksporterte typer bør hete <ComponentName>Props. Dette er en konvensjon som hjelper andre utviklere å forstå hva typen brukes til.

Beste praksis

1. Spre alltid props sist

Sørg for at brukere kan overstyre eventuelle standardprops:

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

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

2. Unngå prop-navnkonflikter

Ikke bruk prop-navn som konflikter med HTML-attributter med mindre du med vilje overstyrer 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. Dokumenter egendefinerte props

Legg til JSDoc-kommentarer for egendefinerte props for bedre utvikleropplevelse:

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;
};