Skip to content
This page has been auto-translated and may contain errors.View in English

Filhos e composição

HTML sempre funcionou por aninhamento: um <select> envolve seus <option>s, um <ul> envolve seus <li>s, um <button> envolve seu rótulo. O elemento do meio é filho do elemento ao seu redor, e os dois trabalham juntos para criar aquilo que você vê. Componentes React components podem funcionar da mesma forma. Em vez de passar tudo através de props nomeadas, um componente pode envolver o conteúdo da forma como elementos nativos fazem, e essa ideia única é o que separa um componente que você configura de um componente que você compõe.

Este capítulo constrói o raciocínio do mesmo jeito que o curso faz, através de um Button para uma biblioteca de componentes: primeiro filhos, depois espalhamento de props, depois os detalhes finais que tornam um componente reutilizável na prática.

Filhos como uma interface

O capítulo Props introduziu children: conteúdo entre as tags de abertura e fechamento de um componente chega como uma prop com esse nome. O que isso te proporciona para reutilização é maior do que parece.

jsx
function Button({ children }) {
  return <button>{children}</button>
}

// uso: <Button>Comprar agora</Button>

O componente decide onde os filhos renderizam, e quem chama o componente decide o que eles são. Compare isso com uma prop text, que funciona até o ponto em que quem chama quer algo além de uma string. Com children, nada impede que passem mais do que texto:

jsx
// uso:
// <Button>
//   <CartIcon />
//   Comprar agora
// </Button>

Um ícone à esquerda do rótulo, sem prop icon, sem opção iconPosition="left", sem nenhuma antecipação do autor do componente. Quer o ícone à direita? Mova-o para o outro lado do texto. Toda prop de configuração que o autor teria precisado inventar desaparece e quem chama escreve simplesmente o markup que quis.

Encaminhando o resto das props

Um Button que renderiza um <button> nativo tem um segundo problema: eventos. Quando quem chama escreve <Button onClick={...}>, esse onClick é uma prop customizada chegando no seu componente; nada acontece a menos que o componente o passe para o verdadeiro <button> por baixo. Você poderia encaminhar onClick manualmente, depois onDoubleClick, depois style, depois className, e ainda assim nunca cobrir tudo que um button nativo aceita. A sintaxe de espalhamento encaminha tudo de uma vez:

jsx
function Button({ children, ...rest }) {
  return <button {...rest}>{children}</button>
}

Desestruture as props que seu componente realmente lida, coleta tudo o mais com a sintaxe rest, e espalhe o rest no elemento subjacente. Toda atributo válido de button que quem chama passa, incluindo event handlers, chega onde deveria. Props que seu componente inventa para si mesmo (um variant, digamos) saem da desestruturação para nunca alcançar o elemento do DOM.

Abrindo espaço para sua própria API

Componentes reutilizáveis geralmente adicionam opções por cima do elemento nativo. O button do curso recebe um size ("sm" ou "lg") e um variant ("success", "warning", "danger"), cada um mapeando para uma classe CSS:

jsx
function Button({ children, size, variant, className, ...rest }) {
  const sizeClass = size ? `button-${size}` : ''
  const variantClass = variant ? `button-${variant}` : ''
  const allClasses = [sizeClass, variantClass, className].filter(Boolean).join(' ')

  return (
    <button className={allClasses} {...rest}>
      {children}
    </button>
  )
}

Essa mesclagem evita um bug sutil. Se o componente define className e quem chama também passa uma, qualquer uma que chegar por último ao elemento vence: espalhe o rest depois de seu próprio className e a classe de quem chama silenciosamente sobrescreve a sua; espalhe antes e a sua sobrescreve a deles. Desestruturar className e mesclar as strings você mesmo é a solução. Bibliotecas como clsx ou classnames existem para fazer exatamente essa mesclagem quando as condições se multiplicam.

Espalhar rest props é um contrato, e tem arestas afiadas. As duas que a seção já mostrou, ordem de espalhamento e props vazadas, são as baratas, embora o vazamento seja mais silencioso do que a maioria das pessoas espera.

O que uma prop vazada realmente faz

Uma prop string em minúsculas como variant chega no nó do DOM como um atributo HTML inválido sem aviso nenhum, que é o caso que morde. React avisa sobre uma prop em camelCase como iconPosition e ainda assim a renderiza; avisa e não renderiza nada para um valor booleano ou um nome de handler em minúsculas como onclick. Um typo em camelCase como onClik não recebe nem o atributo nem o aviso.

ref corta para o outro lado: no React 19 é uma prop ordinária, então viaja junto em ...rest para componentes function e alcança o elemento subjacente sem forwardRef.

A aresta cara é a superfície da API. Uma vez que quem chama pode passar props nativas arbitrárias, vai fazer isso, e cada uma que ele alcançar é algo que você deve a ele na próxima versão; estreitar o passthrough depois é uma breaking change mesmo que você nunca tenha documentado.

O "mega challenge" de Avatar do curso mostra o outro lado da composição: um componente que renderiza de três maneiras diferentes, uma foto quando src é fornecido, iniciais quando children são, um ícone caso contrário. Sobrecarregar assim é apropriado para uma biblioteca de componentes documentada, onde um Avatar flexível bate três componentes quase idênticos. Em código de aplicação, alcançar por ele cedo geralmente sinaliza um componente fazendo muitos jobs; dois componentes pequenos batem um inteligente.

O conflito entre esses dois instintos, adicionar uma prop ou compor de fora, corre através do resto desta seção, e os próximos padrões, compound components e render props, são respostas estruturadas a ele.

JunoDeixe quem chama colocar coisas dentro Um Button que renderiza seus children não precisa de uma prop text, uma prop icon, e uma prop icon-position. Quem quer usá-lo escreve o ícone e o texto dentro das tags na ordem que quer, e o Button coloca esse conteúdo onde pertence.

Aninhamento, a coisa que HTML sempre fez, é também o melhor truque do React para reutilização.

JunoDeixe quem chama colocar coisas dentro Construa componentes reutilizáveis em dois hábitos: renderize children onde o conteúdo vai, e desestruture as props que você lida enquanto espalha ...rest no elemento subjacente para que atributos nativos e event handlers passem através.

Mescle className você mesmo em vez de deixar a ordem de espalhamento decidir qual sobrevive, e mantenha props inventadas como variant fora do objeto rest para que nunca atinjam o DOM.

JunoDeixe quem chama colocar coisas dentro Trate o encaminhamento de rest-prop como parte da superfície da API do seu componente: colisões são resolvidas pela ordem de espalhamento, props customizadas não desestruturadas vazam para o DOM, e quem chama dependerá do passthrough.

Prefira composição através de children sobre props de configuração até uma prop ganhar seu lugar, e reserve componentes sobrecarregados para código de biblioteca documentada onde uma peça flexível realmente substitui várias.

Próximo: Compound components, onde um componente se torna vários que trabalham juntos.