Tutorial de VueJS 3 – Comunicación de eventos con emit
verificado el 2 septiembre 2026 · 3 min
Un componente hijo avisa al padre de un cambio emitiendo un evento: defineEmits lo declara, emit('add-to-cart', id) lo envía y el padre lo escucha con @add-to-cart. Las props bajan, los eventos suben.
Las props bajan del padre al hijo. Para hacer subir una información, un componente emite un evento y el padre lo escucha, exactamente igual que escucharía un clic.
El problema
Desde la división del capítulo 8, el carrito vive en App.vue y el botón en ProductDisplay.vue. El hijo no puede modificar cart: no tiene acceso, y un componente no debe escribir en el estado de otro.
La formulación correcta no es «el hijo añade al carrito», sino «el hijo anuncia que se ha hecho clic y el padre decide qué hacer con ello».
Declarar y emitir
En el hijo, declara los eventos que emite y luego emítelos:
<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>defineEmits es una macro del compilador, como defineProps: no hay que importar nada. Devuelve la función que sirve para emitir.
Declarar los eventos no es obligatorio, pero viene bien: la lista documenta el contrato del componente, el editor la autocompleta y Vue avisa si emites un nombre que no has declarado.
Escuchar en el padre
Un evento personalizado se escucha con la misma sintaxis que un evento nativo:
<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>Haz clic: el contador vuelve a moverse. El botón está en el hijo, el contador en el padre, y ninguno de los dos conoce el estado del otro.
Pasar un dato
Un carrito realista recuerda lo que se mete en él, no solo cuánto. Pasa el identificador de la variante como segundo argumento de emit:
<script setup>
function addToCart() {
emit('add-to-cart', variants.value[selectedVariant.value].id)
}
</script>El padre lo recibe como parámetro de su función:
<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>cart ha pasado a ser un array, así que la vista usa cart.length. Añade varias veces la camiseta azul y comprueba en las herramientas de desarrollo de Vue que el array se va llenando de 2234.
Si son varias informaciones, emite un objeto en lugar de una larga lista de argumentos:
emit('add-to-cart', { id: variant.id, taille: 'M', quantite: 1 })El nombre de los eventos
Escribe el nombre en kebab-case: add-to-cart, no addToCart. Los atributos HTML no distinguen mayúsculas, una plantilla escrita directamente en una página recibiría @addtocart y el listener no se dispararía nunca. En un archivo .vue, Vue corrige la caja, pero mejor coger la buena costumbre.
Un evento personalizado solo se propaga al padre directo. Si tres niveles de componentes separan al emisor del destinatario, hay que volver a emitirlo en cada piso. Cuando eso empieza a molestar, es la señal de que hace falta un estado compartido: exactamente el papel de Pinia.
El sentido de la circulación
Una reseña que llega por este camino se puede después animar al mostrarla.
Quédate con estas dos frases: las props bajan, los eventos suben. El padre es dueño del dato, el hijo pide que se modifique. Eso es lo que hace el flujo predecible y mantiene siempre rastreable el origen de un cambio.
Los eventos se declaran en la opción emits y se emiten con this.$emit('add-to-cart', id). El padre los escucha exactamente igual.
remove-from-cart emitido por un segundo botón, y haz que el padre quite del array el último identificador.Errores frecuentes
@addtocart y el listener no se dispararía nunca. Usa kebab-case.