CodeForge

Maquetación Web Dinámica / TypeScript en la maquetación

Tipar eventos: MouseEvent, KeyboardEvent y más

Teoría20 min20 XP

En el módulo 14 leíste event.key, event.target.value, event.preventDefault(). TypeScript conoce cada tipo de evento y te da su "formulario" exacto con autocompletado — pero también te descubre una trampa clásica: event.target es más ambiguo de lo que parece. Hoy tipas eventos como un profesional.

Cada evento, su propio formulario

La inferencia automática

eventos-tipados.ts
// "click" → TypeScript sabe que event es un MouseEvent
boton.addEventListener("click", (event) => {
event.clientX;    // ✓ coordenada del clic
event.button;     // ✓ qué botón del mouse
});

// "keydown" → KeyboardEvent
input.addEventListener("keydown", (event) => {
event.key;        // ✓ "Enter", "Escape", "a"...
event.shiftKey;   // ✓ ¿estaba Shift presionado?
});

// "submit" → SubmitEvent
form.addEventListener("submit", (event) => {
event.preventDefault();   // ✓ tipado y disponible
});

No escribiste ni un tipo, y aun así event viene con el correcto: como TS conoce que "keydown" produce un KeyboardEvent, dentro del callback el autocompletado te ofrece .key y .shiftKey, y te impediría pedir .clientX (que no existe en teclado). El editor te guía sin que hagas nada.

La trampa: event.target es ambiguo

Aquí está el detalle que confunde a todos. event.target (quién ORIGINÓ el evento) tiene un tipo muy genérico —EventTarget | null— porque en teoría podría ser cualquier cosa. Así que pedirle .value da error:

target.ts
input.addEventListener("input", (event) => {
event.target.value;
// ❌ Error: Property 'value' does not exist on type 'EventTarget'

// SOLUCIÓN A: aserción de tipo (sabes que el target es un input)
const objetivo = event.target as HTMLInputElement;
objetivo.value;   // ✓

// SOLUCIÓN B (a menudo más limpia): usa la variable del closure
input.value;      // ✓ 'input' ya está tipado como HTMLInputElement
});

Dos salidas: castear event.target as HTMLInputElement cuando lo necesites (útil en delegación de eventos, donde no tienes la variable), o —más limpio cuando lo tienes— usar directamente la variable ya tipada del closure (input, que definiste con su tipo). Existe también event.currentTarget (el elemento donde PUSISTE el listener, no necesariamente el que se clicó), útil pero con sus propios matices de tipo; para empezar, castear target o usar el closure te resuelve casi todo.

En acción (el JS resultante corriendo)

En un listener de 'input', escribes event.target.value y TS marca error: 'value does not exist on EventTarget'. ¿Por qué, y cuál es una solución limpia?

Mini-reto

Tipa un pequeño buscador: un input con listener input (usa KeyboardEvent/Event según corresponda y castea event.target), y un form con submit que haga preventDefault. En tu editor, borra el tipo del target y observa el error de .value; escribe event. dentro de cada callback y mira cómo el autocompletado cambia según el tipo de evento. Sentir esa asistencia es entender por qué se tipa.

Qué sigue

Tienes las tres piezas: elementos tipados, null manejado, eventos tipados. Es hora de PONERLAS a trabajar juntas: la próxima lección toma los patrones de UI del módulo 14 —el modal, las tabs, el dark mode— y los reescribe en TypeScript, para que veas el mismo resultado pero blindado contra los errores tontos.