Capítulo 9 de 11

Tutorial VueJS 3 – Comunicar eventos com defineEmits

verificado a 2 Setembro 2026 · 3 min

Resposta rápida

Um componente filho assinala uma alteração ao pai emitindo um evento: defineEmits declara-o, emit('add-to-cart', id) envia-o e o pai escuta-o com @add-to-cart. Os props descem, os eventos sobem.

No fim deste capítulo, saberás fazer subir uma informação de um componente para o seu pai.

Os props descem do pai para o filho. Para fazer subir uma informação, um componente emite um evento e o pai escuta-o, exatamente como escutaria um clique.

O problema

Desde a divisão feita no capítulo 8, o carrinho vive em App.vue e o botão em ProductDisplay.vue. O filho não pode modificar cart: não lhe tem acesso, e um componente não deve escrever no estado de outro.

A formulação certa não é «o filho acrescenta ao carrinho», mas «o filho anuncia que houve um clique, o pai decide o que fazer com isso».

Declarar e emitir

No filho, declara os eventos emitidos e depois emite:

src/components/ProductDisplay.vue
<script setup>
const emit = defineEmits(['add-to-cart'])

function addToCart() {
  emit('add-to-cart')
}
</script>

<template>
  <button
    class="button"
    :class="{ disabledButton: !inStock }"
    :disabled="!inStock"
    @click="addToCart"
  >
    Ajouter au panier
  </button>
</template>

O defineEmits é uma macro do compilador, tal como o defineProps: sem import. Devolve a função que serve para emitir.

Declarar os eventos não é obrigatório, mas compensa: a lista documenta o contrato do componente, o editor completa-a automaticamente e o Vue avisa se emitires um nome não declarado.

Escutar do lado do pai

Um evento personalizado escuta-se com a mesma sintaxe de um evento nativo:

src/App.vue
<script setup>
import { ref } from 'vue'
import ProductDisplay from './components/ProductDisplay.vue'

const cart = ref(0)

function updateCart() {
  cart.value += 1
}
</script>

<template>
  <div class="cart">Panier ({{ cart }})</div>
  <ProductDisplay :premium="premium" @add-to-cart="updateCart" />
</template>

Clica: o contador volta a andar. O botão está no filho, o contador no pai, e nenhum dos dois conhece o estado do outro.

Transmitir um dado

Um carrinho realista guarda o que lá pomos, não apenas quantos. Passa o identificador da variante como segundo argumento do emit:

src/components/ProductDisplay.vue
<script setup>
function addToCart() {
  emit('add-to-cart', variants.value[selectedVariant.value].id)
}
</script>

O pai recebe-o como parâmetro da sua função:

src/App.vue
<script setup>
const cart = ref([])

function updateCart(id) {
  cart.value.push(id)
}
</script>

<template>
  <div class="cart">Panier ({{ cart.length }})</div>
  <ProductDisplay :premium="premium" @add-to-cart="updateCart" />
</template>

O cart passou a ser um array, por isso a apresentação usa cart.length. Acrescenta várias vezes a T-shirt azul e confirma nas ferramentas de desenvolvimento do Vue que o array se enche mesmo de 2234.

Para várias informações, emite um objeto em vez de uma longa lista de argumentos:

javascript
emit('add-to-cart', { id: variant.id, taille: 'M', quantite: 1 })

O nome dos eventos

Escreve o nome em kebab-case: add-to-cart, não addToCart. Os atributos HTML não distinguem maiúsculas de minúsculas, um template escrito diretamente numa página receberia @addtocart e o listener nunca chegaria a disparar. Num ficheiro .vue, o Vue corrige a caixa, mas mais vale ganhar já o hábito certo.

Os eventos não sobem sozinhos

Um evento personalizado só se propaga ao pai direto. Se três níveis de componentes separarem o emissor do destinatário, é preciso reemiti-lo em cada andar. Quando isso se torna maçador, é sinal de que precisas de um estado partilhado: é exatamente esse o papel do Pinia.

O sentido de circulação

Uma avaliação que chega por este caminho pode depois ser animada ao aparecer.

Retém estas duas frases: os props descem, os eventos sobem. O pai é dono do dado, o filho pede que ele seja alterado. É isso que torna o fluxo previsível e a origem de uma alteração sempre rastreável.

Na Options API

Os eventos declaram-se na opção emits e emitem-se com this.$emit('add-to-cart', id). O pai escuta-os exatamente da mesma maneira.

ExercícioAcrescenta um evento remove-from-cart emitido por um segundo botão e faz com que o pai retire do array o último identificador.

Erros frequentes

Dar ao evento um nome em camelCase Os atributos HTML ignoram a caixa: um template fora de um ficheiro .vue receberia @addtocart e o listener nunca chegaria a disparar. Usa o kebab-case.
Esperar que um evento atravesse vários níveis Um evento só vai até ao pai direto. Para além de dois níveis, passa a um estado partilhado com o Pinia.
Modificar o estado do pai a partir do filho O filho assinala, o pai decide. É isso que mantém rastreável a origem das alterações.
Newsletter

Os novos testes, tutoriais e projetos, por e-mail.

Testes reproduzíveis, código versionado, resultados datados. Nunca spam.