Los manejadores de eventos DOM en templates de Vue reciben un objeto Event, pero TypeScript no sabe qué elemento específico lo disparó. Necesitas tipar el parámetro del evento explícitamente y hacer un cast de event.target al tipo de elemento correcto para acceder a propiedades como .value o .checked.
El problema
// error en modo estricto: 'any' implícito
function handleInput(event) {
console.log(event.target.value)
}
// error: 'value' no existe en EventTarget
function handleInput(event: Event) {
console.log(event.target.value)
}event.target tiene el tipo EventTarget | null, que no tiene la propiedad .value. Necesitas una aserción de tipo.
La solución
<script setup lang="ts">
function handleInput(event: Event) {
const target = event.target as HTMLInputElement
console.log(target.value)
}
function handleClick(event: MouseEvent) {
console.log(event.clientX, event.clientY)
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter') {
submit()
}
}
function handleSubmit(event: SubmitEvent) {
event.preventDefault()
const form = event.target as HTMLFormElement
const data = new FormData(form)
}
</script>
<template>
<input @input="handleInput" />
<button @click="handleClick">Click</button>
<input @keydown="handleKeydown" />
<form @submit="handleSubmit">...</form>
</template>Referencia de tipos de evento
| Evento en el template | Tipo TypeScript | Propiedades clave |
|---|---|---|
@click, @dblclick | MouseEvent | clientX, clientY, button, ctrlKey |
@keydown, @keyup | KeyboardEvent | key, code, ctrlKey, shiftKey |
@input, @change | Event | target (requiere cast) |
@focus, @blur | FocusEvent | relatedTarget |
@submit | SubmitEvent | submitter |
@drag, @drop | DragEvent | dataTransfer |
@wheel | WheelEvent | deltaX, deltaY |
@touchstart, @touchend | TouchEvent | touches, changedTouches |
Aserciones de tipo por elemento
Los distintos elementos de formulario necesitan casts diferentes:
function handleTextInput(event: Event) {
const input = event.target as HTMLInputElement
console.log(input.value)
}
function handleCheckbox(event: Event) {
const checkbox = event.target as HTMLInputElement
console.log(checkbox.checked)
}
function handleSelect(event: Event) {
const select = event.target as HTMLSelectElement
console.log(select.value, select.selectedIndex)
}
function handleTextarea(event: Event) {
const textarea = event.target as HTMLTextAreaElement
console.log(textarea.value)
}Manejadores inline
Para casos simples, haz el cast directamente en el template:
<template>
<input @input="name = ($event.target as HTMLInputElement).value" />
</template>O usa una arrow function inline:
<template>
<input @input="(e: Event) => name = (e.target as HTMLInputElement).value" />
</template>Eventos de componentes personalizados
Los eventos de componentes Vue (no eventos DOM) se tipan a través de defineEmits. No se necesita cast porque el tipo del payload lo define el hijo:
<!-- ChildComponent.vue -->
<script setup lang="ts">
const emit = defineEmits<{
select: [item: { id: number; name: string }]
}>()
</script><!-- Parent.vue -->
<script setup lang="ts">
function handleSelect(item: { id: number; name: string }) {
console.log(item.id) // completamente tipado
}
</script>
<template>
<ChildComponent @select="handleSelect" />
</template>target vs currentTarget
function handleClick(event: MouseEvent) {
// target: el elemento real que se clicó (puede ser un hijo)
const target = event.target as HTMLElement
// currentTarget: el elemento al que está asociado el listener
const button = event.currentTarget as HTMLButtonElement
}Si tienes un botón con un span dentro, al clicar el span, target es el span y currentTarget es el botón. Usa currentTarget cuando quieras el elemento al que está adjunto el manejador.