Search buenodc

Nesta página
  1. Importante
  2. Contextualização
  3. Como começar
  4. Como funciona
  5. Cronograma
  6. Missão 1 — Props: alimentando o componente com dados do pai
  7. Missão 2 — Listagem dinâmica com v-for + props
  8. Missão 3 — Emits: o filho fala com o pai
  9. Missão 4 — Desafio: FiltroVagas como componente
  10. Entrega individual

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

AulaFocoMissões
1TeoriaSlides — Props e Comunicação entre Componentes
2PropsMissões 1 e 2
3EmitsMissão 3
4DesafioMissã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:

  1. Declare as props no <script setup>:
    const props = defineProps(["titulo", "empresa", "stack", "nivel", "remoto"]);
  2. Substitua os textos fixos no <template> pelas props: {{ titulo }}, {{ empresa }}, etc.
  3. Use v-if="remoto" para exibir o ícone de remoto apenas quando a prop for true.

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 apenas remoto="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 declarar id nas 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 prop id — ela foi declarada no defineProps do 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:

  1. Adicione uma ref para armazenar a confirmação:
    import { ref } from "vue";
    const confirmacao = ref("");
  2. Crie a função que recebe o título da vaga:
    function receberCandidatura(titulo) {
      confirmacao.value = `Candidatura enviada para: ${titulo}`;
    }
  3. Conecte o evento ao componente:
    <VagaCard
      v-for="vaga in vagas"
      :key="vaga.id"
      v-bind="vaga"
      @candidatura="receberCandidatura"
    />
  4. 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:

  1. Importe e use <FiltroVagas /> antes da lista de cards

  2. Escute o evento @filtroAlterado e armazene o nível recebido numa ref:

    const filtroAtual = ref("Todos");
  3. Crie uma computed que 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),
    );
  4. Substitua vagas por vagasFiltradas no v-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.