Atividade Props e Emits — VagaHub
Disciplina: Programação Web 2 Assunto: Props e Comunicação entre Componentes Professor: Me. Danilo Bueno Grupos: duplas Duração: 4 aulas de 50 minutos em sala (+ 1 aula de teoria)
Importante
Contextualização
A VagaHub é uma plataforma de vagas para desenvolvedores. Até agora o site deles era uma página HTML estática que o time de RH atualizava manualmente toda semana — um <div> por vaga, escrito na mão.
Com o crescimento da plataforma, o time de engenharia migrou para Vue 3 + Vite e criou um array com as vagas disponíveis. O problema: o componente de card ainda exibe dados fixos no código, então todos os cards mostram a mesma vaga.
Você foi contratado como dev front-end para corrigir isso — aplicando props e emits para que cada card receba seus dados corretamente e consiga se comunicar com a tela principal.
Como começar
Após criar o projeto na Missão 1, crie o arquivo src/data/vagas.js com o conteúdo abaixo:
export const vagas = [
{
id: 1,
titulo: "Dev Front-end",
empresa: "TechCorp",
stack: "Vue 3",
nivel: "Júnior",
remoto: true,
},
{
id: 2,
titulo: "Dev Full-stack",
empresa: "DevHouse",
stack: "React / Node",
nivel: "Pleno",
remoto: false,
},
{
id: 3,
titulo: "UI Engineer",
empresa: "PixelLab",
stack: "Vue 3 / Figma",
nivel: "Pleno",
remoto: true,
},
{
id: 4,
titulo: "Dev Front-end Sênior",
empresa: "CoreSys",
stack: "React",
nivel: "Sênior",
remoto: false,
},
{
id: 5,
titulo: "Dev Front-end",
empresa: "StartupX",
stack: "Vue 3",
nivel: "Júnior",
remoto: true,
},
];
Como funciona
- Trabalhem em dupla num único projeto
- Cada integrante responde o questionário individual no Moodle
- Ao final, entreguem o projeto compactado (sem a pasta
node_modules/)
Cronograma
| Aula | Foco | Missões |
|---|---|---|
| 1 | Teoria | Slides — Props e Comunicação entre Componentes |
| 2 | Props | Missões 1 e 2 |
| 3 | Emits | Missão 3 |
| 4 | Desafio | Missão 4 |
Missão 1 — Props: alimentando o componente com dados do pai
A VagaHub já tem o array de vagas pronto. Agora precisamos de um componente que saiba receber esses dados.
Tarefa 1.1 — Criar o projeto
Crie um novo projeto Vue 3 com Vite:
npm create vite@latest vagahub -- --template vue --no-interactive
cd vagahub
npm install
npm run dev
Confirme que o projeto abre em http://localhost:5173. Em seguida, limpe o App.vue deixando apenas a estrutura básica:
<template>
<main>
<h1>VagaHub</h1>
</main>
</template>
<script setup></script>
<style scoped></style>
Crie também a pasta src/data/ e adicione o arquivo vagas.js com o array fornecido em “Como começar”.
Tarefa 1.2 — Criar VagaCard.vue com dados hardcoded
Crie o arquivo src/components/VagaCard.vue com dados fixos por enquanto:
<template>
<div class="card">
<h2>Dev Front-end</h2>
<p>TechCorp</p>
<span class="stack">Vue 3</span>
<span class="nivel">Júnior</span>
<span class="remoto">🏠 Remoto</span>
</div>
</template>
<script setup></script>
<style scoped>
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 1rem;
margin: 0.5rem 0;
}
.nivel {
font-weight: bold;
}
.stack {
color: #555;
}
</style>
Importe e use <VagaCard /> dentro do App.vue. Confirme que o card aparece na tela.
Tarefa 1.3 — Declarar props e receber dados do pai
O card ainda exibe dados fixos. Vamos corrigi-lo.
No VagaCard.vue, substitua os dados hardcoded por props:
- Declare as props no
<script setup>:const props = defineProps(["titulo", "empresa", "stack", "nivel", "remoto"]); - Substitua os textos fixos no
<template>pelas props:{{ titulo }},{{ empresa }}, etc. - Use
v-if="remoto"para exibir o ícone de remoto apenas quando a prop fortrue.
No App.vue, passe os dados ao usar o componente:
<VagaCard
titulo="Dev Front-end"
empresa="TechCorp"
stack="Vue 3"
nivel="Júnior"
:remoto="true"
/>
Observe que
:remoto="true"usa:(v-bind). O que acontece se você remover o:e escrever apenasremoto="true"? O valor recebido no filho é o mesmo?
O que acontece se o pai passar uma prop que o filho não declarou em
defineProps? Experimente passar:id="1"sem declararidnas props e observe o comportamento no console do navegador.
Missão 2 — Listagem dinâmica com v-for + props
Com o componente recebendo props, o próximo passo é usá-lo para renderizar todas as vagas do array dinamicamente.
Tarefa 2.1 — Listar vagas com v-for
No App.vue, importe o array e use v-for para renderizar um VagaCard para cada vaga. Passe as props individualmente:
<VagaCard
v-for="vaga in vagas"
:key="vaga.id"
:titulo="vaga.titulo"
:empresa="vaga.empresa"
:stack="vaga.stack"
:nivel="vaga.nivel"
:remoto="vaga.remoto"
/>
Confirme que os 5 cards aparecem na tela, cada um com os dados corretos.
Tarefa 2.2 — Simplificar com v-bind=“vaga”
Quando o objeto tem as mesmas chaves que as props do filho, existe um atalho. Substitua as 5 linhas de :prop="vaga.prop" por uma única linha:
<VagaCard v-for="vaga in vagas" :key="vaga.id" v-bind="vaga" />
Confirme que os cards continuam exibindo os dados corretamente.
Qual é a diferença entre
v-bind="vaga"e passar cada prop individualmente com:titulo="vaga.titulo"? O que acontece com a propid— ela foi declarada nodefinePropsdo VagaCard?
Missão 3 — Emits: o filho fala com o pai
O botão “Candidatar” precisa informar ao App.vue qual vaga foi selecionada para que a tela principal exiba uma confirmação.
Tarefa 3.1 — Adicionar botão e declarar emit
No VagaCard.vue, adicione um botão no template e declare o emit no <script setup>:
<!-- no template -->
<button @click="emit('candidatura', titulo)">Candidatar</button>
<!-- no script setup -->
const emit = defineEmits(['candidatura'])
Confirme que o botão aparece em todos os cards. Ao clicar, ainda não acontece nada visível — o evento está sendo emitido, mas ninguém está escutando.
Tarefa 3.2 — Escutar o evento no pai
No App.vue, escute o evento candidatura emitido pelo filho:
- Adicione uma
refpara armazenar a confirmação:import { ref } from "vue"; const confirmacao = ref(""); - Crie a função que recebe o título da vaga:
function receberCandidatura(titulo) { confirmacao.value = `Candidatura enviada para: ${titulo}`; } - Conecte o evento ao componente:
<VagaCard v-for="vaga in vagas" :key="vaga.id" v-bind="vaga" @candidatura="receberCandidatura" /> - Exiba a confirmação no template:
<p v-if="confirmacao">✅ {{ confirmacao }}</p>
Clique em “Candidatar” em diferentes cards e confirme que a mensagem muda corretamente.
Tarefa 3.3 — Investigar o fluxo unidirecional
Dentro do VagaCard.vue, tente modificar o valor de uma prop diretamente:
function testar() {
props.titulo = "Vaga modificada";
}
Adicione um botão temporário para chamar essa função. O que aparece no console do navegador? O valor na tela muda? Remova o código de teste ao terminar.
Por que o Vue não permite modificar props diretamente? O que poderia dar errado numa aplicação com vários componentes se cada filho pudesse alterar livremente os dados do pai?
Missão 4 — Desafio: FiltroVagas como componente
A VagaHub quer que os usuários possam filtrar as vagas por nível. Em vez de colocar o filtro direto no App.vue, a equipe quer um componente separado FiltroVagas.vue — reutilizável e independente.
Tarefa 4.1 — Criar FiltroVagas.vue
Crie src/components/FiltroVagas.vue com botões para cada nível. Quando um botão for clicado, o componente deve emitir o nível selecionado — ele não sabe o que fazer com essa informação; isso é responsabilidade do pai.
<template>
<div class="filtro">
<button
v-for="nivel in niveis"
:key="nivel"
@click="emit('filtroAlterado', nivel)"
>
{{ nivel }}
</button>
</div>
</template>
<script setup>
const emit = defineEmits(["filtroAlterado"]);
const niveis = ["Todos", "Júnior", "Pleno", "Sênior"];
</script>
Tarefa 4.2 — Conectar o filtro ao App.vue
No App.vue:
-
Importe e use
<FiltroVagas />antes da lista de cards -
Escute o evento
@filtroAlteradoe armazene o nível recebido numaref:const filtroAtual = ref("Todos"); -
Crie uma
computedque filtra o array conforme o nível selecionado:import { ref, computed } from "vue"; const vagasFiltradas = computed(() => filtroAtual.value === "Todos" ? vagas : vagas.filter((v) => v.nivel === filtroAtual.value), ); -
Substitua
vagasporvagasFiltradasnov-for.
Teste os botões de filtro e confirme que a lista atualiza corretamente.
Tarefa 4.3 — Desafio opcional
Adicione uma prop candidatada (booleana) ao VagaCard. Após o usuário clicar em “Candidatar”, o App.vue deve marcar aquela vaga como candidatada e passar :candidatada="true" para o card correspondente.
No VagaCard, use essa prop para mudar o visual do card (ex: fundo diferente, botão desabilitado, texto “Candidatura enviada”).
Entrega individual
Cada integrante da dupla deve responder o Questionário VagaHub no Moodle individualmente.