Componentes 🧩

Esta página acompanha os slides, na mesma ordem. São quatro partes: criar um projeto, entender o arquivo .vue, enviar dados ao filho e receber avisos do filho. A matéria vem primeiro 📖. Os exercícios práticos 🛠️ ficam juntos no final da página.

A aula de Vue.js usou uma página HTML e a CDN. Aqui o mesmo Vue passa a viver em arquivos .vue, do jeito que um projeto real é escrito.

Parte 1: Criando um projeto Vue 🚀

Por que sair da página solta 📄

Um arquivo .vue reúne, num lugar só, a tela, a lógica e o CSS de um componente. O navegador lê HTML, CSS e JavaScript. Quem faz a tradução é o Vite ⚡, a ferramenta de construção que o Vue usa por padrão. Com ela, salvar o arquivo atualiza a página aberta, e o endereço da aplicação usa http, como o de um site de verdade.

O que o computador precisa 🧰

Três ferramentas, e a aula pode começar 🎉.

  1. 💻 VS Code, o editor da disciplina.
  2. 🎨 A extensão Vue - Official. No VS Code, abra o painel de extensões, procure esse nome e instale a que tem mais downloads. Ela colore o arquivo .vue e completa o código enquanto você digita. Materiais mais antigos chamam essa extensão de Volar. É a mesma família.
  3. 🟢 Node.js, o mesmo runtime da aula de Node. A documentação atual do Vue aceita a linha 22 a partir da versão 22.18, e também a versão 24.12 ou posterior. As versões da linha 23, e as da linha 24 anteriores a 24.12, ficam de fora dessa faixa.

No terminal:

node -v

O número que aparecer é a versão instalada. Se o comando não for encontrado, ou se a versão estiver fora da faixa, instale uma versão atual em nodejs.org e abra um terminal novo.

Criar o projeto ✨

O comando oficial pergunta o nome do projeto e monta a pasta. Na primeira vez ele baixa a ferramenta, então o computador precisa de internet.

  1. Crie uma pasta para reunir os projetos da disciplina, por exemplo projetos, e abra essa pasta no VS Code.
  2. Abra o terminal pelo menu Terminal, Novo Terminal.
  3. Confira se o terminal está na pasta certa. No macOS e no Linux, ls. No Windows, dir.
  4. Rode npm create vue@latest.
  5. Quando pedir o nome, escreva ola-vue.
  6. O assistente oferece ferramentas extras: TypeScript, JSX, Vue Router, Pinia, testes, ESLint, Prettier e Vue DevTools. Escolha Não em todas. Enter confirma a opção marcada. O projeto fica pequeno e fácil de ler.
  7. No fim, o terminal sugere os próximos comandos. Rode, nesta ordem:
cd ola-vue
npm install
npm run dev

npm install baixa as bibliotecas do projeto. npm run dev liga o servidor de desenvolvimento. O terminal imprime um endereço local, em geral http://localhost:5173. Se o navegador não abrir sozinho, copie esse endereço. A página que aparece é o exemplo que o próprio Vue gerou 🎉.

Deixe esse terminal aberto enquanto trabalha. Cada salvamento no editor atualiza a tela. Para encerrar o servidor, use Ctrl+C nesse terminal.

Parte 2: Arquivos Vue e componentes 🗂️

O componente 🧩

Um componente é um trecho da interface com uma função clara. Pode ser uma saudação, um cartão de livro ou um formulário. Sozinho, ele é pequeno. Junto com outros, forma a página.

O mesmo componente pode aparecer várias vezes, e cada uso pode receber dados diferentes. No Vue, ele fica gravado num arquivo .vue.

O projeto que você acabou de criar já mostra um componente na tela: o arquivo src/App.vue. Os componentes novos entram nele. Os outros arquivos gerados podem permanecer na pasta. A tela mostra só o que App.vue usa.

O arquivo .vue 📁

Esse arquivo também é chamado de SFC, sigla em inglês para componente de arquivo único. Três blocos ficam juntos:

  • <template> descreve o HTML do componente. Vale o que você já usou na aula de Vue: {{ }}, v-if, v-for, v-model e @click.
  • <script setup> guarda os dados e as funções. O que for declarado ali fica disponível no template. Essa escrita é a forma atual do Vue 3, a Composition API. As variáveis e as funções ficam declaradas direto nesse script, no lugar de data() e methods da aula com CDN.
  • <style scoped> guarda o CSS do componente. Com scoped, a regra vale para o HTML deste arquivo. Outro componente pode ter um <p> de outra cor.

A ordem dos blocos é livre. Nos exemplos desta aula o template vem primeiro, para a tela aparecer antes da lógica. O projeto gerado às vezes traz o script no topo. Os dois jeitos funcionam.

Um primeiro componente, src/components/Saudacao.vue:

<template>
  <p>Olá, {{ curso }}!</p>
</template>

<script setup>
const curso = 'CPW2'
</script>

<style scoped>
p {
  color: teal;
}
</style>

Leia de cima para baixo. O template escreve um parágrafo e pede o valor de curso. O script declara essa constante. O style pinta o parágrafo de verde azulado, e só o deste componente.

curso é texto fixo. Uma constante dá conta disso. Quando o valor muda e a tela precisa acompanhar, entra o ref, na parte 3.

Usar o componente 🔗

App.vue importa o arquivo e usa o nome dele como tag. O import já registra o componente. A inicial maiúscula ajuda o olho: <Saudacao /> é um componente nosso, <p> é HTML.

Substitua o conteúdo de src/App.vue por:

<template>
  <Saudacao />
</template>

<script setup>
import Saudacao from './components/Saudacao.vue'
</script>

O nome da tag, o nome do import e o nome do arquivo se encontram: Saudacao, sem acento, em Saudacao.vue. Salve os dois arquivos. A página que já está aberta passa a mostrar “Olá, CPW2!” 👋.

Parte 3: Propriedades 🏷️

Props ⬇️

Até aqui a saudação traz o curso escrito dentro do próprio componente. O mais útil é o componente receber o dado de quem o usa. Esse dado se chama prop, de property, propriedade.

A prop vai do componente pai para o componente filho. O pai escolhe o valor. O filho declara o nome da prop e mostra esse valor na tela.

defineProps declara a prop que o componente aceita:

<template>
  <p>Olá, {{ nome }}!</p>
</template>

<script setup>
defineProps({
  nome: {
    type: String,
    required: true
  }
})
</script>

type: String diz que nome é texto. required: true pede que o pai envie esse texto sempre. No template, nome aparece solto, como a constante curso aparecia antes.

No pai, o texto fixo vai entre aspas, sem dois pontos:

<template>
  <Saudacao nome="Ana" />
</template>

<script setup>
import Saudacao from './components/Saudacao.vue'
</script>

A tela mostra “Olá, Ana!”.

Texto fixo e v-bind 🔗

Quando o nome mora numa variável, a tag usa v-bind. O atalho é os dois pontos, o mesmo : da aula de Vue:

<Saudacao :nome="aluno" />

nome="Ana" entrega o texto Ana. :nome="aluno" entrega o valor da variável aluno. nome="aluno", com aspas e sem os dois pontos, entrega a palavra aluno, letra por letra.

ref 🔄

No projeto, o dado que muda fica numa ref. Ela faz o papel de data() da aula com CDN: o Vue observa o valor e atualiza a tela quando ele troca.

<template>
  <Saudacao :nome="aluno" />
</template>

<script setup>
import { ref } from 'vue'
import Saudacao from './components/Saudacao.vue'

const aluno = ref('Ana')
</script>

ref vem do pacote vue. No script, o texto está em aluno.value. No template, escreva aluno. O Vue lê o valor por você. Troque 'Ana' por outro nome, salve, e a saudação acompanha.

O mesmo componente, dois usos 👥

Duas tags, dois valores, um arquivo só:

<template>
  <Saudacao nome="Ana" />
  <Saudacao nome="Bruno" />
</template>

A tela mostra “Olá, Ana!” e “Olá, Bruno!”, as duas a partir do mesmo arquivo.

Parte 4: Emissão de eventos 📤

O evento ⬆️

A prop desce: o pai envia o valor, o filho mostra. O clique, porém, acontece dentro do filho. Quem atualiza um dado do pai é o próprio pai. O filho avisa que algo ocorreu, e o pai decide o que fazer com o aviso.

O aviso é um evento. Nesta aula o evento se chama cumprimentou.

Disparar com emit 📤

defineEmits lista os eventos que o componente pode disparar. A função emit dispara o evento:

<script setup>
const emit = defineEmits(['cumprimentou'])

function avisar() {
  emit('cumprimentou')
}
</script>

<template>
  <button @click="avisar">Dizer olá</button>
</template>

@click continua sendo o clique do mouse, como na aula de Vue. emit é o passo seguinte: contar ao pai que esse clique importa.

O pai escuta o evento 👂

Na tag, @cumprimentou escuta o evento. É o mesmo atalho de v-on que você já usa em @click, agora com um nome criado por você.

<template>
  <Saudacao nome="Ana" @cumprimentou="registrar" />
  <p>{{ recado }}</p>
</template>

<script setup>
import { ref } from 'vue'
import Saudacao from './components/Saudacao.vue'

const recado = ref('Ninguém cumprimentou ainda.')

function registrar() {
  recado.value = 'A mensagem chegou.'
}
</script>

O parágrafo do recado mora no pai. A função registrar também. O botão mora no filho. O clique percorre esse caminho: botão, avisar, emit, @cumprimentou, registrar, recado.

O evento leva o nome 💬

O emit aceita um segundo argumento, o conteúdo do recado. Para ler a prop dentro do script, guarde o retorno de defineProps:

<script setup>
const props = defineProps({
  nome: {
    type: String,
    required: true
  }
})

const emit = defineEmits(['cumprimentou'])

function avisar() {
  emit('cumprimentou', props.nome)
}
</script>

No template, o nome segue solto: {{ nome }}. No script, ele fica em props.nome.

O pai recebe esse texto no parâmetro da função:

function registrar(nome) {
  recado.value = nome + ' cumprimentou.'
}

Com <Saudacao nome="Ana" @cumprimentou="registrar" />, o clique troca o parágrafo para “Ana cumprimentou.”.

Nomes de uma palavra bastam por enquanto. Se um evento precisar de duas, a tag e o emit usam kebab-case, com o mesmo texto nos dois lados: emit('livro-salvo') e @livro-salvo.

A aula de Vue com CDN escrevia a aplicação como um objeto, com data() e methods. Um arquivo .vue aceita esse formato: o objeto começa com export default e o <script> fica sem setup. O exemplo completo fecha esta parte, depois dos dois arquivos com <script setup>.

Os dois arquivos juntos 🤝

src/components/Saudacao.vue:

<template>
  <div>
    <p>Olá, {{ nome }}!</p>
    <button @click="avisar">Dizer olá</button>
  </div>
</template>

<script setup>
const props = defineProps({
  nome: {
    type: String,
    required: true
  }
})

const emit = defineEmits(['cumprimentou'])

function avisar() {
  emit('cumprimentou', props.nome)
}
</script>

<style scoped>
p {
  color: teal;
}
</style>

src/App.vue:

<template>
  <Saudacao nome="Ana" @cumprimentou="registrar" />
  <Saudacao nome="Bruno" @cumprimentou="registrar" />
  <p>{{ recado }}</p>
</template>

<script setup>
import { ref } from 'vue'
import Saudacao from './components/Saudacao.vue'

const recado = ref('Ninguém cumprimentou ainda.')

function registrar(nome) {
  recado.value = nome + ' cumprimentou.'
}
</script>

Ana e Bruno são o mesmo componente, com props diferentes. O clique de cada botão chama a mesma função do pai e envia o próprio nome.

O caminho dos dados 🗺️:

flowchart TD
    pai[Pai] -->|props| filho[Filho]
    filho -->|evento| pai

Props descem ⬇️. Eventos sobem ⬆️. Cada dado tem um dono, e a tela fica previsível 🎯.

A mesma tela com export default

src/components/Saudacao.vue:

<template>
  <div>
    <p>Olá, {{ nome }}!</p>
    <button @click="avisar">Dizer olá</button>
  </div>
</template>

<script>
export default {
  props: {
    nome: {
      type: String,
      required: true
    }
  },
  emits: ['cumprimentou'],
  methods: {
    avisar() {
      this.$emit('cumprimentou', this.nome)
    }
  }
}
</script>

<style scoped>
p {
  color: teal;
}
</style>

src/App.vue:

<template>
  <Saudacao nome="Ana" @cumprimentou="registrar" />
  <Saudacao nome="Bruno" @cumprimentou="registrar" />
  <p>{{ recado }}</p>
</template>

<script>
import Saudacao from './components/Saudacao.vue'

export default {
  components: {
    Saudacao
  },
  data() {
    return {
      recado: 'Ninguém cumprimentou ainda.'
    }
  },
  methods: {
    registrar(nome) {
      this.recado = nome + ' cumprimentou.'
    }
  }
}
</script>

A tela faz o mesmo. O clique da Ana mostra “Ana cumprimentou.” e o do Bruno mostra “Bruno cumprimentou.”.

No filho, a prop fica dentro de props. No template, nome continua solto. No método, o valor é this.nome. O evento entra na lista emits e sai por this.$emit.

No pai, recado nasce em data() e a função fica em methods. A atribuição é this.recado = ..., sem .value. O import sozinho não cria a tag. O pai registra o filho em components.

Exercícios de fixação práticos 🛠️

A matéria fica acima. Os doze exercícios seguem a ordem das quatro partes 💪. Salve cada mudança e olhe o navegador.

Parte 1: criar o projeto 🚀

Exercício 1. O Node responde 🟢

No terminal, rode node -v.

👀 Resultado esperado: uma linha com a versão, dentro da faixa 22.18 ou posterior na linha 22, ou 24.12 ou posterior.

💡 Dica: se a mensagem disser que o comando não existe, instale o Node.js e abra outro terminal. Um terminal antigo continua sem enxergar o programa novo.

Exercício 2. A pasta do projeto 📁

Crie o projeto ola-vue com npm create vue@latest, respondendo Não às perguntas extras. Se você já criou essa pasta ao ler a parte 1, use a mesma.

👀 Resultado esperado: existe a pasta ola-vue e, dentro dela, o arquivo package.json.

💡 Dica: rode o comando na pasta que guarda vários projetos. Ele mesmo cria ola-vue. Rodar de novo dentro de ola-vue criaria um projeto dentro do outro.

Exercício 3. A página no navegador 🌐

Dentro de ola-vue, rode npm install e depois npm run dev.

👀 Resultado esperado: o terminal mostra um endereço local e o navegador exibe a página inicial do Vue.

💡 Dica: use o endereço impresso no terminal, mesmo que a porta não seja

  1. Mantenha esse terminal aberto para os exercícios seguintes.

Parte 2: o componente 🧩

O servidor da parte 1 continua ligado. Cada exercício é uma mudança pequena. Salve e olhe o navegador.

Exercício 4. Um título só 📰

Substitua todo o conteúdo de src/App.vue por um template com <h1>Meu primeiro componente</h1> e um <script setup> vazio.

👀 Resultado esperado: a página inicial do Vue some e fica só esse título.

💡 Dica: apague o conteúdo anterior do arquivo. Um trecho antigo de import impede a página de abrir.

Exercício 5. O título vira componente 🧩

Crie src/components/Saudacao.vue com o mesmo <h1> no template. Em App.vue, importe Saudacao e troque o <h1> pela tag <Saudacao />.

👀 Resultado esperado: o título continua na tela, agora vindo do arquivo novo.

💡 Dica: o caminho do import, a partir de src/App.vue, é ./components/Saudacao.vue. O nome do arquivo vai sem acento.

Exercício 6. O CSS do componente 🎨

No Saudacao.vue, acrescente um bloco <style scoped> que pinte o h1 de teal.

👀 Resultado esperado: o título fica verde azulado.

💡 Dica: a regra CSS fica no arquivo do componente, junto do template dele.

Parte 3: props 🏷️

Exercício 7. A prop nome 🏷️

Em Saudacao.vue, troque o título fixo por Olá, {{ nome }}!. Declare a prop nome como String obrigatória. Em App.vue, use <Saudacao nome="Ana" />.

👀 Resultado esperado: a tela mostra “Olá, Ana!” em verde azulado.

💡 Dica: nome no template é a prop. As aspas em nome="Ana" mandam o texto fixo.

Exercício 8. O nome sai de uma ref 🔄

Crie const aluno = ref('Ana') no script de App.vue e passe :nome="aluno". Importe ref de vue.

👀 Resultado esperado: a saudação continua “Olá, Ana!”. Mude o texto dentro do ref, salve, e o título muda junto.

💡 Dica: os dois pontos ligam a prop à variável. No script, o valor fica em aluno.value. Na tag, use aluno.

Exercício 9. Duas pessoas 👥

Mostre duas tags Saudacao, uma com nome="Ana" e outra com nome="Bruno".

👀 Resultado esperado: dois títulos, “Olá, Ana!” e “Olá, Bruno!”.

💡 Dica: as duas tags usam o mesmo arquivo. Cada uma recebe o próprio nome.

Parte 4: eventos 📤

Exercício 10. O filho só avisa 📣

Volte a uma única Saudacao, com nome="Ana". No filho, coloque um botão “Dizer olá” que emite cumprimentou, ainda sem enviar o nome. No pai, um parágrafo começa com “Esperando um clique.” e a função do evento troca esse texto para “A mensagem chegou.”.

👀 Resultado esperado: o clique muda o parágrafo que está em App.vue.

💡 Dica: a função que escreve em recado.value fica no pai. O filho só chama emit.

Exercício 11. O nome no evento 💬

Passe props.nome como segundo argumento de emit. No pai, o parâmetro da função monta a frase com esse nome.

👀 Resultado esperado: o clique mostra “Ana cumprimentou.”.

💡 Dica: guarde defineProps em const props para usar props.nome no script. No template, nome continua solto.

Exercício 12. Duas saudações, um pai 👥

Inclua de novo a segunda saudação, com nome="Bruno", ouvindo o mesmo registrar.

👀 Resultado esperado: o botão da Ana mostra “Ana cumprimentou.” e o botão do Bruno mostra “Bruno cumprimentou.”.

💡 Dica: as duas tags apontam para a mesma função. O nome que chega é o do componente que disparou o evento.

Exercício de fixação teórico 🧠

Referências 📚

Rodrigo Prestes Machado
CC BY 4.0 DEED

Copyright © 2026 RPM Hub. Distributed by CC-BY-4.0 license.

This site uses Just the Docs, a documentation theme for Jekyll.