Fermer

octobre 16, 2023

Création de composants polymorphes dans TypeScript

Création de composants polymorphes dans TypeScript


Dans ce petit conseil, extrait de Libérer la puissance de TypeScriptSteve vous montre comment utiliser les composants polymorphes dans TypeScript.

Dans mon article Extension des propriétés d’un élément HTML dans TypeScriptje vous ai dit qu’au cours de la création d’une application volumineuse, j’ai tendance à finir par créer quelques wrappers autour des composants. Box est un wrapper primitif autour des éléments de bloc de base en HTML (tels que <div>, <aside>, <section>, <article>, <main>, <head>, et ainsi de suite). Mais tout comme nous ne voulons pas perdre toute la signification sémantique que nous tirons de ces balises, nous n’avons pas non plus besoin de multiples variantes de Box tout cela est fondamentalement pareil. Ce que nous aimerions faire, c’est utiliser Box mais aussi pouvoir préciser ce qu’il doit y avoir sous le capot. UN composant polymorphe est un composant unique adaptable qui peut représenter différents éléments HTML sémantiques, TypeScript s’adaptant automatiquement à ces changements.

Voici une version trop simplifiée d’un Box élément inspiré de Composants stylisés.

Et voici un exemple de Box composant de Pâte, Celui de Twilio système de conception :

<Box as="article" backgroundColor="colorBackgroundBody" padding="space60">
  Parent box on the hill side
  <Box
    backgroundColor="colorBackgroundSuccessWeakest"
    display="inline-block"
    padding="space40"
  >
    nested box 1 made out of ticky tacky
  </Box>
</Box>

Voici une implémentation simple qui ne passe par aucun des accessoires, comme nous l’avons fait avec Button et LabelledInputProps au-dessus de:

import { PropsWithChildren } from 'react';

type BoxProps = PropsWithChildren<{
  as: 'div' | 'section' | 'article' | 'p';
}>;

const Box = ({ as, children }: BoxProps) => {
  const TagName = as || 'div';
  return <TagName>{children}</TagName>;
};

export default Box;

Nous allons bien as à TagName, qui est un nom de composant valide dans JSX. Cela fonctionne en ce qui concerne React, mais nous voulons également que TypeScript s’adapte en conséquence à l’élément que nous définissons dans le as soutenir:

import { ComponentProps } from 'react';

type BoxProps = ComponentProps<'div'> & {
  as: 'div' | 'section' | 'article' | 'p';
};

const Box = ({ as, children }: BoxProps) => {
  const TagName = as || 'div';
  return <TagName>{children}</TagName>;
};

export default Box;

Honnêtement, je ne sais même pas si des éléments comme <section> avoir des propriétés qu’un <div> n’a pas. Même si je suis sûr de pouvoir le rechercher, aucun de nous n’est satisfait de cette mise en œuvre.

Mais qu’est-ce que c’est 'div' être transmis là-dedans et comment ça marche ? Si nous regardons la définition du type pour ComponentPropsWithRefnous voyons ce qui suit :

type ComponentPropsWithRef<T extends ElementType> = T extends new (
  props: infer P,
) => Component<any, any>
  ? PropsWithoutRef<P> & RefAttributes<InstanceType<T>>
  : PropsWithRef<ComponentProps<T>>;

Nous pouvons ignorer tous ces ternaires. Nous sommes intéressés par ElementType tout de suite:

type BoxProps = ComponentPropsWithRef<'div'> & {
  as: ElementType;
};

Une liste complétée automatiquement de tous les types d'éléments

D’accord, c’est intéressant, mais et si nous voulions le type d’argument que nous donnons à ComponentProps être le même que… as?

Nous pourrait essayez quelque chose comme ceci :

import { ComponentProps, ElementType } from 'react';

type BoxProps<E extends ElementType> = Omit<ComponentProps<E>, 'as'> & {
  as?: E;
};

const Box = <E extends ElementType="div">({ as, ...props }: BoxProps<E>) => {
  const TagName = as || 'div';
  return <TagName {...props} />;
};

export default Box;

Maintenant, un Box Le composant s’adaptera à n’importe quel type d’élément que nous transmettons avec le as soutenir.

Une boîte avec les accessoires d'un bouton

Nous pouvons maintenant utiliser notre Box composant partout où nous pourrions autrement utiliser un <div>:

<Box as="section" className="flex place-content-between w-full">
  <Button className="button" onClick={decrement}>
    Decrement
  </Button>
  <Button onClick={reset}>Reset</Button>
  <Button onClick={increment}>Increment</Button>
</Box>

Vous pouvez voir le résultat final sur le polymorphic branche du dépôt GitHub pour ce tutoriel.

Cet article est extrait de Libérer la puissance de TypeScriptdisponible sur SitePoint Premium et chez les détaillants de livres électroniques.






Source link

octobre 16, 2023