Componentes reutilizáveis: quando vale criar e quando é exagero
Veja critérios práticos para criar componentes reutilizáveis sem cair em abstrações prematuras, APIs confusas ou variações difíceis de manter.
Reutilização útil nasce de significado, mudanças compartilhadas e um contrato claro — não apenas de trechos visualmente parecidos.
Componentização é uma das primeiras ideias que aprendemos no desenvolvimento frontend. Em vez de repetir interface e comportamento, criamos uma unidade com nome, propriedades e responsabilidades próprias. Quando essa divisão funciona, uma mudança importante pode ser feita em um lugar e aplicada com consistência em toda a interface.
O problema começa quando `reutilizável` vira um objetivo antes de existir um padrão real para reutilizar. Dois trechos parecidos aparecem, extraímos um componente genérico e logo surgem propriedades para esconder um título, trocar a ordem, remover o espaçamento, mudar o clique e permitir uma exceção que só existe em uma tela.
O código duplicado diminui, mas a decisão deixa de estar no lugar em que é usada. Para entender uma tela simples, passamos a interpretar uma API ampla e uma sequência de condicionais. Por isso, a pergunta que venho usando não é apenas `isso se repete?`. É `essa abstração torna o significado, a mudança e o uso mais simples?`
Reutilização não é o mesmo que remover toda duplicação
Duplicação é um sinal para observar, não uma ordem automática de refatoração. Duas estruturas podem ter o mesmo HTML hoje e representar conceitos diferentes.
Um card de projeto e um card de artigo, por exemplo, podem começar com imagem, título, resumo e link. Ainda assim, a hierarquia de informação, os estados, as ações e o comportamento responsivo podem evoluir em direções diferentes. Colocar os dois dentro de um `UniversalCard` cedo demais conecta mudanças que talvez nunca devessem estar conectadas.
O contrário também acontece. Elementos que não aparecem muitas vezes podem concentrar uma regra importante. Um botão usado em poucas telas ainda pode merecer um componente quando precisa garantir os mesmos estados de carregamento, foco, desabilitado, ícone e área de clique.
A evidência mais útil não é uma contagem rígida de duas ou três repetições. É perceber se as implementações têm o mesmo significado e se mudanças nelas precisam continuar acontecendo juntas. Essa postura se aproxima do princípio AHA, de Kent C. Dodds, e do alerta de Sandi Metz sobre a abstração errada.
O que um componente precisa simplificar
Um bom componente cria um limite compreensível. Quem usa deve perceber o propósito pelo nome e fornecer apenas as informações necessárias. Por dentro, o componente pode cuidar de detalhes repetitivos, estados e regras de acessibilidade.
- Significado: transforma uma combinação genérica de elementos em um conceito reconhecível.
- Mudança: concentra ajustes que realmente precisam permanecer consistentes.
- Interação: mantém teclado, foco, carregamento, feedback e estados desabilitados com o mesmo comportamento.
- Uso: oferece uma API menor e mais clara do que a implementação interna.
- Composição: define onde conteúdo e variações reais podem entrar sem prever todas as telas possíveis.
Se o componente apenas move o código para outro arquivo e exige que a tela continue conhecendo todos os detalhes internos, talvez ele ainda não tenha criado uma abstração útil. A ideia se conecta ao conceito de módulos profundos de John Ousterhout: uma interface deve ser consideravelmente mais simples do que a funcionalidade e a complexidade que ela esconde.
Uma escada de abstração
Em vez de escolher entre copiar tudo e criar um design system, prefiro pensar em quatro níveis. O avanço acontece quando há evidência suficiente para assumir um contrato mais compartilhado.
1. Implementação local
O código fica na própria tela enquanto o problema ainda está sendo entendido. Uma pequena duplicação pode preservar contexto e permitir que cada caso evolua sem uma conexão artificial.
2. Componente local
Quando um bloco já tem nome e responsabilidade próprios, ele pode ser extraído para organizar a tela, mesmo que ainda seja usado uma única vez. O objetivo aqui é legibilidade e isolamento, não reutilização global.
3. Componente compartilhado
Depois que o mesmo conceito aparece em mais de um contexto e as mudanças começam a se repetir, vale promover o componente. Nesse ponto, casos reais já orientam propriedades, slots, composição e testes.
4. Primitivo de design system
Elementos fundamentais como botão, campo, badge ou modal podem ganhar regras mais amplas de aparência, interação e acessibilidade. Esse nível pede contrato estável, documentação e manutenção consciente, porque uma mudança afeta muitas partes do produto.
Não é obrigatório chegar ao design system. A abstração deve parar no nível em que continua ajudando.
Sinais de que vale extrair um componente
- O bloco representa o mesmo conceito em diferentes lugares.
- Correções ou ajustes precisam ser repetidos nas mesmas implementações.
- Existe um conjunto pequeno e conhecido de variações.
- O nome do componente comunica melhor a intenção do que o markup aberto.
- Há regras de interação ou acessibilidade que precisam permanecer consistentes.
- A interface de uso pode ser menor do que os detalhes escondidos.
- Os casos atuais permitem definir um contrato sem adivinhar necessidades futuras.
Imagine um botão que precisa bloquear cliques durante uma ação, mostrar carregamento sem alterar a largura, manter foco visível e aceitar ícone em posições conhecidas. Concentrar essas regras em um `BaseButton` reduz mais do que linhas duplicadas: reduz maneiras diferentes de implementar o mesmo comportamento.
1<BaseButton variant="primary" :loading="saving">2 Salvar alterações3</BaseButton>O valor está no contrato reconhecível. A tela informa intenção, variante e estado; o componente cuida dos detalhes comuns.
Sinais de abstração prematura
- Foi criada para dois trechos apenas visualmente parecidos.
- O nome é tão genérico que não explica o conceito.
- Cada novo uso adiciona um booleano ou uma propriedade de exceção.
- Combinações de propriedades produzem estados difíceis de prever.
- A tela precisa conhecer a estrutura interna para usar o componente.
- Slots, callbacks e propriedades apenas repassam tudo para outro elemento.
- Mudanças de um contexto complicam outros contextos sem relação.
- A implementação compartilhada é mais difícil de entender do que as versões separadas.
Um componente universal pode parecer flexível, mas a quantidade de controles costuma mostrar que conceitos diferentes foram reunidos por aparência.
1<UniversalCard2 :clickable="true"3 :show-header="false"4 :compact="isMobile"5 :hide-footer="!hasActions"6 :reverse-media="featured"7 :remove-padding="customLayout"8 :use-special-border="campaignMode"9/>Componentes mais específicos, como `ArticleCard` e `ProjectCard`, podem duplicar parte da estrutura e ainda assim produzir uma arquitetura mais clara.
Variações reais antes de propriedades imaginadas
Propriedades deveriam representar decisões que já existem no domínio do componente. Um botão pode ter variantes `primary`, `secondary` e `ghost` porque esses papéis aparecem de forma consistente. Criar combinações para telas hipotéticas aumenta a API sem valor imediato.
- É uma variação legítima do mesmo conceito?
- Essa variação deve se comportar como as anteriores?
- O nome da propriedade deixa a intenção clara?
- A combinação com outras propriedades continua previsível?
- Seria mais simples compor por fora ou criar um componente específico?
Propriedades de escape como `noPadding`, `customClass`, `disableDefaultBehavior` e `specialMode` não são sempre erradas. Porém, quando se acumulam, mostram que o limite original talvez precise ser revisto.
Quando extrair comportamento em vez de interface
Nem toda repetição pertence a um componente visual. Se duas telas compartilham carregamento, transformação de dados ou controle de estado, mas apresentam resultados diferentes, a abstração pode ser uma função, um composable no Vue ou um hook no React.
A documentação do Vue recomenda composables para reutilizar lógica e componentes quando lógica e layout visual precisam ser reutilizados juntos. A documentação do React segue direção semelhante ao tratar custom Hooks como uma forma de compartilhar lógica por meio de casos de uso concretos e bem nomeados.
Também é possível compartilhar uma parte menor. Em vez de um formulário universal, talvez faça sentido ter um `FormField` responsável por rótulo, descrição, mensagem de erro e associação acessível, enquanto cada tela compõe os campos de que precisa.
Um fluxo prático para decidir
- Qual conceito estou nomeando? Se a resposta for apenas `um bloco parecido`, ainda falta clareza.
- Quais mudanças precisam acontecer juntas? O histórico mostra o acoplamento real.
- Quais variações já existem? A API deve nascer de casos concretos.
- O uso ficará mais simples? A chamada precisa revelar intenção, não apenas deslocar complexidade.
- A interação precisa ser consistente? Foco, teclado, feedback e estados podem justificar compartilhamento cedo.
- A lógica é visual? Talvez uma função, composable ou hook seja um limite melhor.
- Consigo começar local? Um componente pode amadurecer perto do primeiro uso.
- Consigo desfazer a decisão? Dividir ou remover uma abstração que deixou de ajudar deve continuar possível.
Uma boa abstração reduz decisões repetidas. Se exige mais contexto, exceções e combinações do que os casos que substituiu, talvez seja cedo demais.
Referências e leituras recomendadas
Estas fontes não formam uma regra única para componentização. Elas oferecem perspectivas complementares sobre abstração guiada por casos reais, limites de módulos, refatoração incremental, hierarquia de componentes e reutilização de lógica.
Para uma leitura inicial mais curta, eu começaria por `The Wrong Abstraction`, seguiria com a documentação do framework usado no projeto e depois avançaria para `A Philosophy of Software Design`.
Limites e ressalvas
Projetos diferentes têm custos diferentes. Em uma interface pequena, uma duplicação controlada pode ser mais barata do que manter uma camada compartilhada. Em um produto com muitas equipes, inconsistências em elementos básicos podem justificar componentes e documentação mais cedo.
Componentes de uso único também não são automaticamente ruins. Eles podem organizar uma tela extensa, isolar estado ou dar nome a uma seção importante. O erro seria chamar toda extração de reutilização ou promover cada componente local para uma biblioteca comum.
Da mesma forma, uma API com muitas propriedades não é necessariamente inadequada quando o domínio realmente possui muitas variações. O sinal de alerta aparece quando essas variações não têm relação clara, produzem combinações inválidas ou existem apenas para preservar uma abstração antiga.
Conclusão
Componentes reutilizáveis ajudam quando conectam implementações que compartilham significado, mudança e comportamento. Eles atrapalham quando conectam apenas semelhanças temporárias.
O caminho mais seguro costuma ser incremental: implementar com clareza, observar mudanças, extrair um componente local, promover quando o padrão se repetir e criar um primitivo global apenas quando o contrato estiver estável.
O principal aprendizado é que reutilização não se mede apenas por quantas linhas desapareceram. Se a abstração exige mais contexto, exceções e combinações do que os casos que substituiu, manter duas implementações simples pode ser a escolha mais técnica.
