Capítulo 5 de 11

Vue 3: gestionar eventos con v-on (@click, $event y modificadores)

verificado el 7 septiembre 2026 · 6 min

Respuesta rápida

v-on:click, o su atajo @click, ejecuta código cuando se produce el evento. Pasa el nombre de una función para recibir el evento nativo, o llámala con sus argumentos y $event si necesitas ambos. Los modificadores .prevent, .stop, .once, .enter o .esc sustituyen el código repetitivo de gestión de eventos.

Al final de este capítulo, sabrás reaccionar a los clics, al paso del ratón y a las teclas del teclado, pasar argumentos a tus gestores, recuperar el evento nativo y usar los modificadores para escribir menos código.

Por ahora, la aplicación muestra cosas. No reacciona a nada. La directiva v-on asocia un listener de eventos a un elemento y ejecuta código cuando el evento se produce. Este capítulo cubre todo lo que se hace con ella a diario: un clic, el paso del ratón, una tecla, con o sin argumento, y los modificadores que evitan escribir preventDefault() a mano.

El carrito

Añade un contador y un botón:

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

const product = ref('T-Shirt')
const image = ref('/images/t-shirt-bleu.svg')
const inventory = ref(100)
const cart = ref(0)
const details = ref(['60 % coton', '30 % laine', '10 % polyester'])
const variants = ref([
  { id: 2234, color: 'Bleu', image: '/images/t-shirt-bleu.svg' },
  { id: 2235, color: 'Rouge', image: '/images/t-shirt-rouge.svg' }
])
</script>

<template>
  <div class="product-display">
    <div class="cart">Panier ({{ cart }})</div>
    …
    <button class="button">Ajouter au panier</button>
  </div>
</template>

Escuchar un clic

v-on: seguido del nombre del evento, con el código que hay que ejecutar entre comillas:

src/App.vue
<button class="button" v-on:click="cart += 1">Ajouter au panier</button>

Haz clic: el contador sube. El nombre del evento es el del DOM estándar, sin prefijo: click, submit, input, keyup, mouseover.

Llamar a una función

Una expresión en línea sirve para una operación trivial. En cuanto la lógica ocupa más de una línea, escribe una función:

src/App.vue
<script setup>
function addToCart() {
  cart.value += 1
}
</script>

<template>
  <button class="button" v-on:click="addToCart">Ajouter au panier</button>
</template>

Fíjate en el .value del script: manipulamos la referencia, no el valor desenvuelto. En la plantilla se escribe siempre {{ cart }}, sin .value.

¿Con o sin paréntesis?

@click="addToCart" pasa la función en sí: Vue la llama con el evento nativo como primer argumento. @click="addToCart()" la llama sin argumentos. Aquí funcionan las dos. El error clásico aparece cuando la función espera un valor: @click="updateImage(variant.image)" es correcto, pero olvidar los paréntesis pasaría el evento en lugar de la ruta de la imagen.

El atajo con arroba

Igual que v-bind tiene :, v-on tiene @:

src/App.vue
<button class="button" @click="addToCart">Ajouter au panier</button>

Es la forma que verás en toda la documentación y en el resto de este tutorial.

Pasar un argumento

Hagamos que la imagen cambie al pasar el ratón por un color. La función recibe la ruta de la imagen de la variante sobre la que está el ratón:

src/App.vue
<script setup>
function updateImage(variantImage) {
  image.value = variantImage
}
</script>

<template>
  <div
    v-for="variant in variants"
    :key="variant.id"
    @mouseover="updateImage(variant.image)"
  >
    {{ variant.color }}
  </div>
</template>

Pasa el ratón por «Rouge»: la imagen se vuelve roja. Aquí se combinan dos mecanismos: el bucle del capítulo 4 y el enlace de atributos del capítulo 2.

Recuperar el evento nativo

Sin argumentos, la función recibe el evento del DOM. Con argumentos, pídelo explícitamente con $event:

html
<button @click="voir">Sans argument</button>
<button @click="voirAvec('bleu', $event)">Avec arguments</button>
javascript
function voir(event) {
  console.log(event.target)
}

function voirAvec(couleur, event) {
  console.log(couleur, event.target)
}

El objeto recibido es el evento nativo del navegador, sin envoltorio: event.target, event.key y event.preventDefault() funcionan igual que en JavaScript puro.

Los modificadores de evento

Vue añade sufijos que evitan el código repetitivo de gestión de eventos:

html
<form @submit.prevent="envoyer">…</form>   <!-- event.preventDefault() -->
<div @click.stop="…">…</div>               <!-- event.stopPropagation() -->
<div @click.self="…">…</div>               <!-- seulement si la cible est cet élément -->
<button @click.once="…">…</button>         <!-- une seule fois -->
<input @keyup.enter="valider">             <!-- seulement la touche Entrée -->

La lista completa, con lo que sustituye cada modificador:

Modificador Equivalente en JavaScript Uso típico
.prevent event.preventDefault() un formulario que no debe recargar la página
.stop event.stopPropagation() un botón dentro de una tarjeta clicable: el clic no sube hasta la tarjeta
.self if (event.target !== event.currentTarget) return cerrar una ventana modal al hacer clic en el fondo, no en su contenido
.once addEventListener(…, { once: true }) un botón «Realizar pedido» que no debe enviar dos veces
.capture addEventListener(…, { capture: true }) interceptar el evento antes que los hijos
.passive addEventListener(…, { passive: true }) scroll y toque en móvil, para no bloquear el renderizado

Los modificadores se encadenan y se leen de izquierda a derecha: @click.stop.prevent detiene la propagación y después anula el comportamiento por defecto. Este orden importa con .self: @click.self.prevent solo anula el comportamiento por defecto de los clics sobre el propio elemento; @click.prevent.self lo anula para todos los clics, hijos incluidos.

@submit.prevent servirá en el capítulo 10 para impedir que la página se recargue al enviar el formulario.

Modificadores de tecla y de ratón

En los eventos de teclado, un modificador filtra la tecla. Vue acepta cualquier nombre de tecla expuesto por KeyboardEvent.key, escrito en kebab-case, más algunos alias:

html
<input @keyup.enter="valider">
<input @keyup.esc="annuler">
<input @keydown.tab="suivant">
<input @keyup.page-down="pageSuivante">   <!-- KeyboardEvent.key === 'PageDown' -->
<input @keydown.ctrl.enter="envoyer">     <!-- Ctrl + Entrée -->

Los alias disponibles: .enter, .tab, .delete (Supr y Retroceso), .esc, .space, .up, .down, .left, .right. Las teclas de sistema .ctrl, .alt, .shift y .meta se combinan con las demás. Y .exact exige que no haya ninguna otra tecla de sistema pulsada:

html
<button @click.ctrl="ouvrir">…</button>        <!-- Ctrl + clic, même si Shift est aussi enfoncé -->
<button @click.ctrl.exact="ouvrir">…</button>  <!-- Ctrl + clic, et rien d'autre -->
<button @click.exact="ouvrir">…</button>       <!-- clic sans aucune touche système -->

Para el ratón, .left, .right y .middle filtran el botón: @click.right.prevent="menuContextuel" sustituye el menú del navegador por el tuyo.

Varios gestores en un mismo evento

Un evento puede disparar varias funciones, separadas por comas. En ese caso, cada función se llama con paréntesis:

html
<button @click="addToCart(), trackClick('ajout-panier')">Ajouter au panier</button>

Más allá de dos llamadas deja de ser legible: escribe una función que llame a las demás.

Resumen

  • v-on:click o @click: una expresión en línea para una operación trivial; si no, una función.
  • Sin paréntesis, la función recibe el evento nativo; con argumentos, $event lo pasa además.
  • .prevent, .stop, .self y .once sustituyen el código repetitivo; los modificadores de tecla filtran el teclado; .exact fija las combinaciones.
  • En el script, el estado se modifica con .value; en la plantilla, sin él.
En Options API

Las funciones viven en la opción methods y acceden al estado mediante this: methods: { addToCart() { this.cart += 1 } }. La plantilla, en cambio, no cambia.

Los eventos de este capítulo vienen del DOM. Para que un componente hijo avise a su padre, Vue ofrece eventos personalizados con emit: es el tema del capítulo 9, detallado en la guía completa de Vue emit.

EjercicioAñade un botón «Quitar del carrito» que decremente el contador sin bajar nunca de cero. Después sustituye el paso del ratón por un clic en los colores y compara la sensación.

Errores frecuentes

Olvidar .value en una función En el script, cart += 1 no hace nada útil sobre una referencia: escribe cart.value += 1.
Paréntesis que faltan con un argumento @click="updateImage" pasa el evento nativo en lugar del valor esperado. Con un argumento, llama a la función: @click="updateImage(variant.image)".
Recuperar el evento cuando ya se pasa un argumento Hay que pedirlo explícitamente con $event: @click="faire('bleu', $event)".
Encadenar los modificadores en el orden equivocado @click.prevent.self impide el comportamiento por defecto de todos los clics; @click.self.prevent, solo el de aquellos cuyo destino es el propio elemento. El orden se lee de izquierda a derecha.
Usar .passive con .prevent .passive promete al navegador que no se llamará a preventDefault(); combinado con .prevent, el navegador ignora uno de los dos y muestra un aviso.
Newsletter

Las nuevas pruebas, tutoriales y proyectos, por correo.

Pruebas reproducibles, código versionado, resultados fechados. Nunca spam.