Criando componentes polimórficos com React e TypeScript

Jan 05 2023
Adicionar polimorfismo aos seus componentes React os tornará mais versáteis, pois os consumidores poderão definir o elemento renderizado para o DOM. Há etapas importantes a serem seguidas ao construir esses componentes, então vamos começar! Por que precisamos de componentes polimórficos? Você deve ter notado que em algumas bibliotecas como (Material UI ou Chakra UI) alguns componentes têm a opção de permitir um prop as ou elementType.

Adicionar polimorfismo aos seus componentes React os tornará mais versáteis, pois os consumidores poderão definir o elemento renderizado ao arquivo DOM.

Há etapas importantes a serem seguidas ao construir esses componentes, então vamos começar!

Por que precisamos de componentes polimórficos?

Você deve ter notado que em algumas bibliotecas como ( Material UI ou Chakra UI ) alguns componentes têm a opção de permitir um asou elementTypeprop. Isso é importante para personalização, mas também para melhorar a acessibilidade, permitindo-nos usar elementos semânticos HTML ao renderizar nossos componentes.

Adicionando um asprop a um componente React

Vamos ver como poderíamos implementar algo semelhante a essas bibliotecas — começaremos com um Textcomponente React simples.

const Text = ({ as, children, ...rest }) => {
  const Component = as || "span";

  return <Component {...rest}>{children}</Component>;
};

export default function App() {
  return <Text>Text goes here</Text>; // Will render a span by default
}

<Text as="main">Text goes here</Text>;
<Text as="p">Text goes here</Text>;
<Text as="section">Text goes here</Text>;
<Text as="a" href="www.example.com">Text goes here</Text>

Funciona como um encanto! Ah, mas espera…

<Text as="p" href="www.example.com">
    Text goes here
</Text>

      
                

Aí vem o TypeScript

É aqui que entra nosso melhor amigo TypeScript.

Precisamos garantir que as Textprops do componente sejam fortemente tipadas para que possam ser verificadas estaticamente durante o processo de desenvolvimento.

O plano é permitir que nossa asprop aceite qualquer elemento HTML válido e, de alguma forma, determine quais atributos HTML são válidos com base nesse mesmo elemento (por exemplo, uma atag deve permitir o hrefatributo).

Criando nossos tipos

Vamos começar configurando um tipo para o asprop.

import { ElementType } from "react";

type AsProp<T extends ElementType> = {
  /**
   * The HTML element used for the root node.
   */
  as?: T;
};

Nosso tipo Té restrito a tipos que se estendem ElementTypede React, ou seja, quaisquer elementos HTML válidos.

import { ComponentPropsWithoutRef } from "react";

ComponentPropsWithoutRef<T>

Omit<ComponentPropsWithoutRef<T>, keyof Props<T>>

Props<T> & Omit<ComponentPropsWithoutRef<T>, keyof Props<T>>

Nossos tipos combinados devem se parecer com isto:

type TextProps<T extends ElementType> = PropsWithChildren<AsProp<T>> &
  Omit<ComponentPropsWithoutRef<T>, keyof AsProp<T>>;

Finalmente TextPropstem tudo o que precisamos Text! Vamos juntar tudo e adicionar o tipo ao nosso Textcomponente:

import {
  ElementType,
  ComponentPropsWithoutRef,
  PropsWithChildren
} from "react";

type AsProp<T extends ElementType> = {
  /**
   * The HTML element used for the root node.
   */
  as?: T;
};

type TextProps<T extends ElementType> = PropsWithChildren<AsProp<T>> &
  Omit<ComponentPropsWithoutRef<T>, keyof AsProp<T>>;

const Text = <T extends ElementType = "span">({
  as,
  children,
  ...rest
}: TextProps<T>) => {
  const Component = as || "span";

  return <Component {...rest}>{children}</Component>;
};

A melhor coisa sobre nossa configuração é que agora ...resttambém é fortemente tipado.

Então isso deve funcionar:

<Text as="a" href="www.google.com">
   Text goes here
</Text>

<Text as="main" href="www.google.com">
   Text goes here
</Text>