Vue.js: como criar componentes reutilizáveis

Rocketseat

Rocketseat

5 min de leitura
https://prod-files-secure.s3.us-west-2.amazonaws.com/08f749ff-d06d-49a8-a488-9846e081b224/a499d4c2-3f15-4d19-b472-ffc8c9a3ef45/unnamed_%283%29.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAZI2LB466RBSH2H5U%2F20260407%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20260407T091856Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEBkaCXVzLXdlc3QtMiJIMEYCIQD0sJXuspwwoHRZQRXQOYm0RZZlgvl7LThY70%2FZI1%2B8ggIhAILnoVn7moAznbL4qCDa1JNhEfEpVpHs4Tn0AS7cb0TNKogECOH%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEQABoMNjM3NDIzMTgzODA1IgzhLCe9hdDkTZDJmH0q3ANvf038rOmAxOv7LcDLPRww5hfhWLfYQPRZ7J4Bga4RULWzKyDO3hHT0DpH0WsCAbNuSoVRMwAFzGvh%2Bwbt98KN2cRSIy%2FDcOKHlRIPfgN2%2FDU9aXxIBe4s%2FEibk%2BMo%2FMKMwnP1aX1uQXtEzmFGe%2BZCVxhHQDCfcOHa0wwg3EDISo4CcEp6xkepUpgsThAxOWgNNDvFvnS7UobKJG0b3vrJ9kECn57kgJS2waQUZ7k%2F3TzOIumn9gn0Ft%2FrsQKIT%2BRB4lBC88W4RASJzszZMNsET3a8mkCV6fhoJwgMXSIDr8bGw1cVnEfJ2ZuIQYP%2FQuwl1crbBHW%2FJDk1fRrjyVZArNq2S2%2FM7zrvCf2nZ7KnMrxtmsz3kT%2BCeBy7wqdCJrk7pBbr0r%2Bj%2F7V1FUO6540Wv7AEjEg5JMxKS3En5KrFP5erWyQHBd1vC%2BpyG9gTXg5M%2FwiRPKSy%2BXyYhxfFVsWSRr8eiQYkvuZZIOuav312gf0TtQbpsJrzjTAcCax9TU5lzY36HNSgErSUmJR%2BNFy9dbpsTeiij%2Bd38ursuTV87YRbDclc7dhxHNb5BRiFMVn%2BhNqfnwgETpDXNzkvEHnAGJ7Zh1Wt9ldOZL6W1X%2BJMGKGLfP75ye53QK7gDCw%2B9LOBjqkAY6FagD513AoShJLuxhcKHo%2BqqLteJYjYct9%2FDdCS4gtFlhKBwiwxarGoifGNNBA0vmWnyO%2BqrfY9v9Wv6NLP4bnUW20fXL5Sfp%2B7odWKG%2F05xLUjHrmen0LLRJkfG7TEK5XlTewckaiCfKqfQlklbM%2BSNSD5Jn6vf3yanChGMbs9RfHg6EXfpaU0%2FwLuJ1h2ZjXsjqLQrU%2B65Ci%2Fv0pEq%2Fd5aPB&X-Amz-Signature=f5acef74fa73f191127de4b83d14c574ce33fa2f9e39e77bba97b37b9f0f3518&X-Amz-SignedHeaders=host&x-amz-checksum-mode=ENABLED&x-id=GetObject
Fala, dev! 👋 Já se pegou copiando e colando o mesmo bloco de HTML e CSS em cinco arquivos diferentes, só mudando uma corzinha ou um texto? Se a resposta for sim, você está criando o que chamamos de "débito técnico" sem nem perceber.
Neste artigo, vamos resolver isso de uma vez por todas. Você vai aprender a criar componentes reutilizáveis no Vue.js utilizando a Composition API e <script setup>. Vamos sair do básico e construir um componente inteligente que se adapta a diferentes contextos.
Bora codar? 🚀

O que é um componente reutilizável?

Antes de abrir o VS Code, precisamos alinhar o conceito. Um componente reutilizável no Vue.js é um pedaço de interface que funciona de forma isolada, aceita configurações externas (Props) e pode receber conteúdo dinâmico (Slots).
💡 A Analogia do LEGO: Imagine que você está construindo um castelo de LEGO. Você não fabrica um tijolo novo cada vez que precisa levantar uma parede. Você pega um tijolo padrão (o componente) e decide se ele será vermelho, azul ou amarelo (as props).
A ideia é escrever o código uma vez e usar em qualquer lugar. Isso garante:
  • Manutenibilidade: Mudou o design? Muda em um lugar só.
  • Produtividade: Foco na lógica, não em reescrever HTML.
  • Padronização: Todo o app segue o mesmo estilo visual.

O cenário do mundo real: o botão universal

Vamos fugir do "Hello World". Vamos criar um componente que todo sistema precisa: um Botão (BaseButton).
Imagine que no seu e-commerce você tem botões de "Comprar" (verde), "Cancelar" (vermelho) e "Ver Detalhes" (azul). Se você fizer três botões separados, seu código vira uma bagunça. Vamos unificar isso.

1. A estrutura básica (o erro comum)

Um iniciante faria assim no arquivo App.vue:
<button class="btn-primary">Comprar</button> <button class="btn-danger">Excluir</button>
Se o designer pedir para mudar a borda de todos os botões, você terá que editar linha por linha. Vamos criar nosso componente para resolver isso.

Mão na massa: criando o BaseButton.vue

Crie um arquivo chamado BaseButton.vue dentro da pasta components. Vamos usar a sintaxe moderna do Vue 3 (<script setup>).

Passo 1: recebendo dados com Props

As Props são como os parâmetros de uma função. Elas permitem que o componente pai "converse" com o filho, passando dados.
<script setup> // Definindo as propriedades que nosso botão aceita defineProps({ label: { type: String, required: true // Obrigamos a passar um texto }, variant: { type: String, default: 'primary', // Se não passar nada, será 'primary' validator: (value) => ['primary', 'danger', 'outline'].includes(value) } }) </script> <template> <button class="btn" :class="`btn-${variant}`"> {{ label }} </button> </template> <style scoped> .btn { padding: 10px 20px; border-radius: 8px; border: none; cursor: pointer; font-weight: bold; transition: opacity 0.2s; } .btn:hover { opacity: 0.9; } /* Variantes de estilo */ .btn-primary { background-color: #8257e5; color: white; } .btn-danger { background-color: #e83f5b; color: white; } .btn-outline { background-color: transparent; border: 2px solid #8257e5; color: #8257e5; } </style>
⚠️ Atenção: Note o uso do validator. Isso é uma boa prática essencial! Ele garante que ninguém tente passar uma cor que não existe, como btn-abobora. O Vue vai avisar no console se isso acontecer.

Passo 2: Flexibilidade com Slots

E se quisermos adicionar um ícone dentro do botão? Passar HTML via props é má prática e difícil de ler. Para isso, usamos Slots.
O Slot é como um "buraco" no seu componente onde você pode encaixar qualquer coisa.
Vamos atualizar o BaseButton.vue:
<template> <button class="btn" :class="`btn-${variant}`"> <slot> {{ label }} </slot> </button> </template>
Agora o uso fica muito mais poderoso. Veja só como usaríamos no componente pai (App.vue):
<script setup> import BaseButton from './components/BaseButton.vue'; </script> <template> <div class="container"> <BaseButton label="Salvar Alterações" /> <BaseButton variant="danger"> 🗑️ Excluir Produto </BaseButton> <BaseButton variant="outline" label="Voltar" /> </div> </template>

Passo 3: emitindo eventos

Um botão serve para ser clicado, certo? No Vue, os eventos nativos (como @click) são passados automaticamente para o elemento raiz do componente. Mas, é uma boa prática declarar o que seu componente emite para facilitar a leitura.
No BaseButton.vue:
<script setup> // ... props anteriores // Declaramos que este componente emite um evento de clique const emit = defineEmits(['action']) function handleClick() { console.log('Botão clicado no componente filho!'); emit('action'); // Avisa o pai que a ação ocorreu } </script> <template> <button class="btn" :class="`btn-${variant}`" @click="handleClick" > <slot>{{ label }}</slot> </button> </template>
 

Recapitulando: o checklist do componente perfeito

Para garantir que seu componente é realmente profissional, verifique sempre estes 3 pontos:
  1. É genérico? Ele não deve conter lógica de negócio (ex: não chame de BotaoComprar, chame de BaseButton).
  1. Tem validação? Use required e validator nas props para evitar erros bobos.
  1. É flexível? Use Slots para conteúdo e classes dinâmicas para estilo.

Conclusão e próximo passo

Viu como é simples? Transformamos um monte de código repetido em um arquivo limpo, organizado e fácil de manter. Agora, se o design do botão mudar, você altera apenas o BaseButton.vue e a mágica acontece em toda a aplicação. ✨
Desafio para você: Tente criar um componente de Card (BaseCard) que receba um título via prop e tenha um slot para o conteúdo principal.
Se curtiu esse conteúdo e quer dominar mais sobre o ecossistema Vue, continua acompanhando!
Bora codar e até a próxima! 💜

Conheça o Rocketseat Para Empresas

Oferecemos soluções personalizadas para empresas de todos os portes.

Rocketseat

Rocketseat

Ecossistema de educação contínua referência em programação e Inteligência Artificial.

Imagem contendo uma carta e um símbolo de check
NewsletterReceba conteúdos inéditos e novidades gratuitamente