Vue.js: Fundamentos de Componentes
Última atualização: 2026-08-26
Os componentes do Vue são instâncias reutilizáveis do Vue — cada componente encapsula seu próprio modelo, lógica e estilos. A arquitetura central do Vue 3 é a “árvore de componentes”: componente raiz (App.vue) → componentes de página → componentes de negócio → componentes básicos (Button/Input).
Dominar os conceitos básicos dos componentes é um passo crucial na criação de aplicações Vue em grande escala. Esta lição ajudará você a compreender a natureza dos componentes, como registrá-los, as convenções de nomenclatura e por que os SFCs (componentes de arquivo único) são a abordagem recomendada no Vue.
1. O que você vai aprender
- O que é um componente? A relação entre componentes e instâncias do Vue
- As três partes do Componente de Arquivo Único (SFC): template, script e style
- A diferença entre registro global e local e como escolher entre eles
- Convenções de nomenclatura de componentes (PascalCase / kebab-case)
- Reutilização de componentes (evitando código duplicado)
- Conceitos básicos de
propseemit(abordados em detalhes nas próximas duas aulas) - 5 Princípios de projeto de componentes
2. Um backend de comércio eletrônico de 30 páginas afetado pela duplicação de código
(1) Desafio: 30 fichas de produto, 30 vezes que é preciso copiar e colar
A administradora do site de comércio eletrônico da Alice precisava exibir 30 fichas de produtos em várias páginas. Sua abordagem inicial:
<!-- ❌ The "Broken" Version: Copy Product Card 30 times -->
<!-- In HomeView.vue -->
<div class="product-card">
<img src="iphone.jpg">
<h3>iPhone 15 Pro</h3>
<p>$999</p>
<button>Add to Cart</button>
</div>
<div class="product-card">
<img src="macbook.jpg">
<h3>MacBook Pro</h3>
<p>$2499</p>
<button>Add to Cart</button>
</div>
<!-- ... And also 28 more ... -->
<!-- The same code appears again in ProductList.vue, SearchResults.vue, Cart.vue -->
O gerente de produto Charlie acrescenta 5 novos requisitos:
“Alice, preciso alterar o texto do botão de ‘Adicionar ao carrinho’ para ‘Adicionar à sacola’. Além disso, o cartão deve mostrar a disponibilidade do produto. E precisa ser responsivo em dispositivos móveis.”
Alice precisa editar mais de 30 arquivos HTML três vezes = 90 edições. Isso é impossível de manter.
(2) Solução com componente Vue: 1 arquivo ProductCard.vue, reutilizado 30 vezes
<!-- components/ProductCard.vue - 1 Component Definitions -->
<template>
<div class="product-card">
<img :src="product.image">
<h3>{{ product.name }}</h3>
<p>${{ product.price }}</p>
<button :disabled="product.stock === 0" @click="addToCart">
{{ product.stock === 0 ? 'Out of Stock' : 'Add to Bag' }}
</button>
</div>
</template>
<script setup>
const props = defineProps({ product: Object })
const emit = defineEmits(['add-to-cart'])
function addToCart() {
emit('add-to-cart', props.product.id)
}
</script>
<!-- HomeView.vue / ProductList.vue / SearchResults.vue -->
<template>
<ProductCard v-for="product in products" :key="product.id" :product="product" @add-to-cart="handleAdd" />
</template>
1 ProductCard.vue → reutilizado 30 vezes. Para alterar o texto do botão, basta fazer a alteração em um único lugar.
(3) Receita
Após a divisão em componentes:
- Volume do código: 30 × 15 linhas × 3 páginas = 1.350 linhas → 1 componente + 30 linhas × 3 = 90 linhas (-93%)
- 1 alteração: 30 fichas de produtos são atualizadas automaticamente
- Adicionar um novo recurso: basta adicioná-lo ao componente, e ele aparecerá automaticamente em todas as páginas
- Testabilidade: o arquivo ProductCard.vue pode ser testado de forma independente
3. O que é um componente?
(1) Definição
Um componente no Vue é uma unidade de interface do usuário reutilizável, com lógica e estilo próprios. Cada componente consiste em uma instância do Vue, além de seus próprios dados, métodos e ciclo de vida.
graph TB
A[App.vue<br/>Root Component] --> B[HomeView.vue<br/>Home]
A --> C[AboutView.vue<br/>About]
A --> D[DashboardView.vue<br/>Dashboard]
B --> E[ProductCard.vue<br/>Product Card]
B --> F[ProductList.vue<br/>Product List]
B --> G[FilterBar.vue<br/>Filter Bar]
E --> H[Button.vue<br/>Button]
E --> I[Badge.vue<br/>Badge]
F --> E
G --> J[Select.vue<br/>Scroll down]
G --> K[Input.vue<br/>Input]
style A fill:#42b883,color:#fff
style E fill:#42b883,color:#fff
style H fill:#42b883,color:#fff
(2) 5 Principais Características
| Recurso | Descrição |
|---|---|
| Reutilizável | 1 componente pode ser usado em N lugares |
| Encapsulamento | Separar dados, métodos e estilo de forma que não interfiram uns nos outros |
| Modular | Componentes menores combinados para formar componentes maiores |
| Testável | Testes unitários de um único componente |
| Fácil de manter | Alterar 1 lugar = Alterar N lugares |
(3) Componentes x Funções
| Dimensão | Função | Componente |
|---|---|---|
| Componentes reutilizáveis | Lógica de código | Interface do usuário + lógica |
| Entrada | Argumentos (args) | props |
| Saída | retorno | emissão |
| Estado | Variáveis locais | dados / referências |
| Efeitos colaterais | Nenhum | Ganchos de ciclo de vida |
4. Componente de arquivo único (SFC)
(1) O que é o SFC?
SFC (Single File Component) = 1 arquivo .vue = 1 componente. Combinar as seções template, script e style em um único arquivo facilita o gerenciamento.
<!-- ProductCard.vue - SFC Single-File Components -->
<template>
<!-- 1. template:HTML Template(Required) -->
<div class="product-card">
<h3>{{ product.name }}</h3>
<p>${{ product.price }}</p>
</div>
</template>
<script setup>
// 2. script:JS Logic(Required)
import { ref } from 'vue'
const props = defineProps({ product: Object })
const count = ref(0)
</script>
<style scoped>
/* 3. style:CSS Style(Optional) */
.product-card {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 1rem;
}
</style>
(2) Explicação detalhada da Parte 3 do SFC
| Seção | Função | Obrigatória? | Idioma |
|---|---|---|---|
<template> |
Modelo HTML | ✅ | Comando HTML + Vue |
<script setup> |
JS Logic | ✅ | JavaScript / TypeScript |
<style scoped> |
Estilos CSS | ❌ | CSS / SCSS / Less |
(3) SFC versus string HTML
// ❌ No need SFC(String Templates,Vue 2 Style)
Vue.component('my-component', {
template: '<div>{{ msg }}</div>',
data() { return { msg: 'Hello' } }
})
// ✅ Use SFC (Recommended)
// MyComponent.vue
<template>
<div>{{ msg }}</div>
</template>
<script setup>
const msg = 'Hello'
</script>
Vantagens do SFC:
- Recursos do editor (destaque de sintaxe, navegação, autocompletar)
- Suporte a ferramentas de desenvolvimento (recarregamento dinâmico do Vite, verificação de tipos do TypeScript)
- Separação de interesses (1 bloco para HTML, 1 para JS e 1 para CSS)
5. Registro de componentes
(1) Registro global (não recomendado para projetos de grande porte)
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import ProductCard from './components/ProductCard.vue'
const app = createApp(App)
// Global Registration:All components are available <ProductCard />
app.component('ProductCard', ProductCard)
app.mount('#app')
(2) Registro local (recomendado)
<!-- HomeView.vue -->
<script setup>
// 1. Import
import ProductCard from '@/components/ProductCard.vue'
// 2. Use in a template(No registration required,Automatically Available)
</script>
<template>
<ProductCard :product="product" />
</template>
(3) Comparação dos dois métodos de registro
| Dimensão | Registro global | Registro local |
|---|---|---|
| Escopo | Aplicação inteira | Componente atual |
| Localização do código | main.js |
Dentro do componente <script setup> |
| Tree-shaking | ❌ Não compatível | ✅ Compatível |
| Sugestões de tipo | Fraca | Forte (autocompletar do IDE) |
| Recomendação | ⭐⭐ Para plugins | ⭐⭐⭐⭐⭐ Para uso comercial |
6. Convenções de nomenclatura de componentes
(1) 3 Convenções de nomenclatura
| Estilo | Exemplo | Uso | Recomendação |
|---|---|---|---|
| PascalCase | ProductCard.vue |
Importação de JS / Nos modelos | ⭐⭐⭐⭐⭐ |
| kebab-case | product-card.vue |
Nomes de arquivos / Modelos em kebab-case | ⭐⭐⭐ |
| camelCase | productCard.vue |
Não recomendado | ⭐ |
(2) Uso em modelos
<template>
<!-- PascalCase(Recommendations,IDE Friendly) -->
<ProductCard />
<UserProfile />
<!-- kebab-case(Also supported) -->
<product-card />
<user-profile />
<!-- ✅ Either one works.,PascalCase Easier to read -->
</template>
(3) Estrutura de pastas
src/
├── components/ # Public Components(For use across pages)
│ ├── ProductCard.vue
│ ├── Button.vue
│ └── Header.vue
├── views/ # Page Components(For routing)
│ ├── HomeView.vue
│ ├── ProductListView.vue
│ └── DashboardView.vue
├── composables/ # Composite Functions
│ ├── useAuth.ts
│ └── useFetch.ts
├── router/ # Router Configuration
└── App.vue # Root Component
7. Noções básicas sobre props e emit
(1) props: Passadas do pai para o filho
<!-- Parent Component Parent.vue -->
<template>
<ProductCard :product="myProduct" :show-stock="true" />
</template>
<!-- Child component ProductCard.vue -->
<template>
<div>
<h3>{{ product.name }}</h3>
<p v-if="showStock">Stock: {{ product.stock }}</p>
</div>
</template>
<script setup>
// Receive the data passed from the parent component props
const props = defineProps({
product: { type: Object, required: true },
showStock: { type: Boolean, default: false }
})
</script>
(2) emitir: do filho para o pai
<!-- Child component ProductCard.vue -->
<template>
<button @click="handleAdd">Add to Cart</button>
</template>
<script setup>
const emit = defineEmits(['add-to-cart'])
function handleAdd() {
emit('add-to-cart', { id: 1, name: 'iPhone' })
}
</script>
<!-- Parent Component Parent.vue -->
<template>
<ProductCard @add-to-cart="handleAddToCart" />
</template>
<script setup>
function handleAddToCart(product) {
console.log('Add to cart:', product)
}
</script>
(3) Referência rápida: props x emit
| Categoria | API | Finalidade |
|---|---|---|
| Pai → Filho | defineProps() |
Transferência de dados |
| Filho → Pai | defineEmits() |
Notificação de evento |
8. Exemplo completo: reutilização de 5 ProductCards
▶ Exemplo: 1. Componente de arquivo único ProductCard.vue
<!-- src/components/ProductCard.vue -->
<template>
<div :class="['product-card', { 'out-of-stock': product.stock === 0 }]">
<img :src="product.image" :alt="product.name">
<h3>{{ product.name }}</h3>
<p class="price">${{ product.price }}</p>
<span class="stock" v-if="product.stock < 10 && product.stock > 0">
Only {{ product.stock }} left
</span>
<button
:disabled="product.stock === 0"
@click="handleAddToCart"
>
{{ product.stock === 0 ? 'Out of Stock' : 'Add to Cart' }}
</button>
</div>
</template>
<script setup>
const props = defineProps({
product: {
type: Object,
required: true,
validator: (val) => val.id && val.name && val.price
}
})
const emit = defineEmits(['add-to-cart'])
function handleAddToCart() {
if (props.product.stock > 0) {
emit('add-to-cart', props.product.id)
}
}
</script>
<style scoped>
.product-card {
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 1rem;
transition: box-shadow 0.2s;
}
.product-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.out-of-stock {
opacity: 0.6;
}
.price {
color: #42b883;
font-weight: bold;
}
button:disabled {
background: #ccc;
cursor: not-allowed;
}
</style>
▶ Exemplo: 2. Reutilização do ProductCard em 3 páginas
<!-- src/views/HomeView.vue -->
<template>
<h1>Featured Products</h1>
<div class="grid">
<ProductCard
v-for="product in featured"
:key="product.id"
:product="product"
@add-to-cart="handleAdd"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ProductCard from '@/components/ProductCard.vue'
import { useCart } from '@/composables/useCart'
const featured = ref([
{ id: 1, name: 'iPhone 15 Pro', price: 999, stock: 50, image: 'iphone.jpg' },
{ id: 2, name: 'MacBook Pro', price: 2499, stock: 20, image: 'macbook.jpg' }
])
const { addToCart } = useCart()
function handleAdd(productId) {
addToCart(productId)
}
</script>
<!-- src/views/ProductListView.vue -->
<template>
<ProductCard
v-for="product in products"
:key="product.id"
:product="product"
@add-to-cart="handleAdd"
/>
</template>
<script setup>
import ProductCard from '@/components/ProductCard.vue'
// ... Exactly the same citation,Reusable Components
</script>
▶ Exemplo: 3. Comparação entre registro global e registro local
// ❌ main.js Global Registration(Not recommended)
import ProductCard from './components/ProductCard.vue'
app.component('ProductCard', ProductCard) // The entire app is available
// ✅ HomeView.vue Local Registration(Recommendations)
<script setup>
import ProductCard from '@/components/ProductCard.vue'
// Only at HomeView.vue Available in China
</script>
▶ Exemplo: 4. Os 5 principais tipos de adereços
<script setup>
defineProps({
// Basic Types
name: String,
age: Number,
active: Boolean,
// Complex Types
user: Object,
items: Array,
// Required + Default value
title: { type: String, required: true },
pageSize: { type: Number, default: 20 },
// Validator
email: {
type: String,
validator: (val) => val.includes('@')
},
// Custom Types
status: {
type: String,
validator: (val) => ['active', 'inactive'].includes(val)
}
})
</script>
▶ Exemplo: 5. 5 Princípios de design para componentes
| Princípio | Descrição |
|---|---|
| Responsabilidade Única | Um componente faz uma coisa |
| Reutilizável | Interface do usuário abstrata e genérica (botão/campo de entrada/janela modal) |
| Configurável | as propriedades controlam o comportamento, os slots controlam o conteúdo |
| Modular | Componentes menores combinados para formar componentes maiores |
| Fácil de testar | Componentes independentes facilitam os testes unitários |
▶ Exemplo: 6. 5 erros comuns
| Erro | Sintoma | Solução |
|---|---|---|
| Arquivo da lista de componentes | Erro do IDE | Use PascalCase: ProductCard.vue |
| Número excessivo de registros globais | Tamanho maior do pacote | Os registros locais oferecem suporte ao “tree-shaking” |
| O componente é muito grande | É difícil de manter | Divida-o em componentes menores |
| Modificação direta de props | Aviso do Vue | Os props são somente para leitura; use emit em vez disso |
| Esqueci de importar um componente | O modelo exibe “componente desconhecido” | Verifique a importação em <script setup> |
❓ Perguntas Frequentes
P: Quando se deve usar o registro global? R: Apenas em duas situações: (1) Componentes plug-in (como o
<el-button>da Element Plus); (2) Componentes básicos utilizados em toda a aplicação (botão/ícone). Os componentes de negócios devem ser sempre registrados localmente para que o tree-shaking possa surtir efeito.
P: O SFC precisa ter todas as três partes? R:
<template>e<script>são obrigatórios;<style>é opcional. Se um componente consistir apenas em JavaScript (por exemplo, se for apenas para encapsulamento de dados), é possível omitir o modelo (usando a funçãorender).
P: Qual é o tamanho adequado para um arquivo de componente? R: Recomendamos que ele tenha menos de 200 linhas. Se ultrapassar 300 linhas, considere dividi-lo. Quanto mais simples for o componente, mais fácil será mantê-lo.
P: O SFC usa a extensão .vue. Como outras ferramentas a reconhecem? R: O Vite, o Webpack e o Vue CLI reconhecem a extensão .vue por padrão. Para o VS Code, instale a extensão “Vue - Official”.
P: Como faço para testar um único componente? R: Use o Vitest + Vue Test Utils (@vue/test-utils):
mount(ProductCard, { props: { product: mockProduct } }). A Fase 4.6 deste tutorial aborda esse assunto em detalhes.
P: Como faço para usar a biblioteca de componentes (Element Plus)? R: Instale via npm → Registre globalmente no main.js → Use
<el-button>diretamente em seus modelos. Para mais detalhes, consulte o Capítulo 4.7 do Phase.
P: Qual é a diferença entre SFC e React JSX? R: O SFC usa modelos semelhantes ao HTML (próximos aos padrões da web), enquanto o React usa JSX (escrita de HTML com JavaScript). O SFC tem uma curva de aprendizado suave, enquanto o JSX é flexível, mas requer uma etapa de compilação. O Vue 3 também oferece suporte ao JSX (@vitejs/plugin-vue-jsx), mas o SFC é a recomendação padrão.
📖 Resumo
- Um componente é uma instância reutilizável do Vue que encapsula as seções de modelo, script e estilo.
- O SFC (arquivos .vue) é a abordagem recomendada no Vue 3 e conta com amplo suporte de editores e conjuntos de ferramentas.
- Registro local > Registro global (compatível com tree-shaking)
- Convenção de nomenclatura: PascalCase (ProductCard) > kebab-case (product-card)
- props: Pai → Filho (defineProps), emit: Filho → Pai (defineEmits)
- Princípios de projeto de componentes: responsabilidade única, reutilização, configurabilidade, composibilidade, testabilidade
- Estrutura de pastas: components/ (componentes compartilhados), views/ (componentes de página), composables/ (funções composáveis)
📝 Exercícios
-
Questões básicas (Dificuldade: ⭐)
Crie um componente Button
src/components/BaseButton.vue:- props:
text(String),variant(String, 'primário'/'sucesso'/'perigo') - As 3 variantes correspondem a 3 classes
- emitir o evento
click - Use três variantes diferentes no arquivo HomeView.vue
- props:
-
Problemas avançados (Dificuldade: ⭐⭐)
Criar uma lista de produtos + sistema de cartões:
- ProductCard.vue: Cartão de produto único (props: product, emit: add-to-cart)
- ProductList.vue: Contêiner da lista de produtos (v-for + filtragem)
- App.vue: Componente raiz; transmite dados e lida com o evento “adicionar ao carrinho”
- 3 arquivos e 3 componentes utilizados em uma estrutura aninhada
-
Problema desafiador (Dificuldade: ⭐⭐⭐)
Implementar um sistema completo de componentes de “ficha de usuário”:
- Avatar.vue: Componente de avatar (compatível com os modos de URL e iniciais)
- UserCard.vue: Cartão do usuário (com avatar)
- UserList.vue: Lista de usuários (utilizando UserCard)
- App.vue: Componente raiz (Dados + Interação)
- 5 atributos (avatar/nome/e-mail/função/ativo)
- 3 eventos emitidos (editar/excluir/selecionar)
- Isolamento de estilos em escopo