Maquetación Web Dinámica / TypeScript en la maquetación
Tipar eventos: MouseEvent, KeyboardEvent y más
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
// "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:
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)
<form id="form" class="caja">
<input id="campo" type="text" placeholder="Escribe algo" aria-label="Texto">
<p id="info" class="info">teclas: 0</p>
<button type="submit">Enviar</button>
</form>body { font-family: system-ui; padding: 20px; background: #f8fafc; }
.caja { background: white; border: 1px solid #e2e8f0; border-radius: 12px; padding: 20px; max-width: 320px; display: grid; gap: 10px; }
input { padding: 9px 12px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 15px; }
.info { margin: 0; font-size: 13px; color: #64748b; }
button { padding: 9px; border: 0; border-radius: 8px; background: #6d28d9; color: white; font-weight: 600; cursor: pointer; }const form = document.querySelector("#form");
const campo = document.querySelector("#campo");
const info = document.querySelector("#info");
let teclas = 0;
if (form && campo && info) {
// keydown → KeyboardEvent: event.key tipado
campo.addEventListener("keydown", (event) => {
teclas++;
info.textContent = "última tecla: '" + event.key + "' · total: " + teclas;
});
// submit → SubmitEvent: preventDefault tipado
form.addEventListener("submit", (event) => {
event.preventDefault();
info.textContent = "Enviado: " + campo.value;
});
}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.