CodeForge

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

Setup con Vite y tipos del DOM

Teoría22 min20 XP

Para escribir TypeScript necesitas un proyecto que lo compile — y el más rápido de montar es Vite. Con eso listo, atacamos el caso que el maquetador vive a diario: tipar los elementos del DOM. querySelector te devuelve algo genérico (¡y que puede ser null!), y TypeScript te obliga —para tu bien— a ser específico y honesto con esos dos hechos.

Setup en un minuto: Vite

terminal.sh
# crear un proyecto TypeScript vanilla (sin framework)
npm create vite@latest mi-proyecto -- --template vanilla-ts

cd mi-proyecto
npm install
npm run dev      # servidor de desarrollo con recarga en vivo

Eso te da un proyecto con TypeScript ya configurado, compilación instantánea y un tsconfig.json que conviene tener en modo estricto ("strict": true — la plantilla de Vite ya lo trae). El modo estricto es justo el que te obliga a manejar los null y no dejar tipos sueltos: es la red de seguridad completa, y este curso siempre lo asume activo.

querySelector te da una caja genérica

tipar-dom.ts
// SIN especificar: TS lo trata como Element genérico
const generico = document.querySelector(".campo");
generico.value = "";   // ❌ Error: Property 'value' does not exist on type 'Element'

// CON el genérico de querySelector: le dices qué tipo esperas (preferido)
const input = document.querySelector<HTMLInputElement>(".campo");
input.value = "";      // ✓ .value existe... pero ojo, ver el null abajo

// alternativa: aserción "as" cuando estás seguro del tipo
const boton = document.querySelector(".btn") as HTMLButtonElement;

Los tipos que más usarás son intuitivos: HTMLInputElement (tiene .value, .checked), HTMLButtonElement, HTMLFormElement, HTMLImageElement (tiene .src, .alt), HTMLAnchorElement (.href), y el genérico HTMLElement (tiene .style, .classList, .textContent — lo común a todos). El editor te autocompleta el nombre correcto.

El null honesto: querySelector puede no encontrar nada

Aquí TypeScript te salva de un clásico: querySelector devuelve el elemento o null si nada coincide. En modo estricto, TS no te deja ignorarlo:

null-checks.ts
const input = document.querySelector<HTMLInputElement>(".campo");
// el tipo real es: HTMLInputElement | null
input.value = "";   // ❌ Error: 'input' is possibly 'null'

// PATRÓN 1 (el recomendado): comprobar con un if
if (input) {
input.value = "";   // ✓ dentro del if, TS SABE que no es null
}

// PATRÓN 2: optional chaining (si existe, úsalo; si no, no hace nada)
input?.focus();

// PATRÓN 3: aserción "!" — "confía en mí, no es null". Úsalo con CUIDADO
const boton = document.querySelector(".btn")!;   // si te equivocas, revienta en runtime

El patrón del if es el rey: dentro del bloque, TypeScript "estrecha" el tipo y sabe que ya no es null. El ! (non-null assertion) es una promesa tuya al compilador —"esto existe, créeme"— y si te equivocas, vuelve el error de runtime que TS pretendía evitar. Úsalo solo cuando de VERDAD estés seguro (un elemento que tú mismo acabas de crear, por ejemplo).

El resultado, corriendo

Fíjate en el comentario del demo: lo que corre es JavaScript (los tipos se borraron), pero lo ESCRIBISTE en TypeScript con HTMLInputElement y el if de null protegiéndote. El editor te avisó de cualquier error antes; el navegador recibió JS limpio. Esa es la división de trabajo de todo el módulo.

Escribes const input = document.querySelector('#email') y TS marca error al usar input.value: 'possibly null' y 'value does not exist'. ¿Cómo lo resuelves bien?

Mini-reto

Monta un Vite vanilla-ts (npm create vite@latest) y escribe un pequeño script tipado: selecciona un input y un botón con sus tipos correctos, comprueba que no son null, y al hacer clic muestra el valor del input en un párrafo. Prueba a QUITAR el <HTMLInputElement> y mira el error aparecer; prueba a quitar el if del null y mira el otro. Provocar los errores a propósito es la mejor forma de entender qué te está protegiendo.

Qué sigue

Ya tipas elementos. Falta tipar lo que PASA con ellos: los eventos. La próxima lección cubre MouseEvent, KeyboardEvent, SubmitEvent y el event.currentTarget — para que event.key, event.target.value y demás vengan con autocompletado y sin sorpresas.