TypeScript no es un lenguaje nuevo: es JavaScript con una red de seguridad. Escribes el mismo código que ya conoces del nivel 2 y 3, pero le añades tipos que describen qué clase de dato es cada cosa. A cambio, el editor te avisa de los errores mientras escribes, en lugar de descubrirlos cuando la página ya está rota delante de un usuario.
Un ejemplo de los que duelen en JavaScript puro:
// JavaScript: nadie te frena.
// partidas llega como texto (typo, un value de un input...).
const partidas = "120";
// 78 / "120" "funciona" por coerción y devuelve un número raro: el bug no salta
// aquí, sino más tarde, cuando el winrate sale mal en producción.
const ratio = 78 / partidas;Con TypeScript, ese descuido se marca en rojo antes de ejecutar. Veamos cómo.
Anotar un tipo#
Una anotación de tipo se escribe tras dos puntos, justo después del nombre: le dices a TypeScript qué esperas y él vigila que se cumpla. Los tres tipos primitivos que más usarás:
// string: texto entre comillas.
const nombre: string = "Tracer";
// number: cualquier número, entero o decimal.
const partidas: number = 120;
// boolean: solo true o false.
const activa: boolean = true;Si rompes la promesa, TypeScript no te deja pasar:
// Error TS2322: Type 'string' is not assignable to type 'number'.
// Has dicho "esto es un number" y le has dado un texto.
const mal: number = "120";Pruébalo: el editor de abajo type-chequea de verdad. Descomenta la última línea y mira cómo aparece el error en el panel “Problemas” sin ejecutar nada.
Anotar funciones#
Una función se anota en dos sitios: cada parámetro lleva su tipo, y el tipo que devuelve va tras el paréntesis de cierre, también con dos puntos. Así TypeScript vigila a la vez lo que entra y lo que sale.
// victorias: number y partidas: number anotan los parámetros (lo que entra).
// El ": number" tras el paréntesis anota el retorno (lo que sale).
function winrate(victorias: number, partidas: number): number {
// Dentro, victorias y partidas ya son number: la cuenta es segura.
return (victorias / partidas) * 100;
}Los parámetros sí hay que anotarlos: al declararlos no tienen valor, así que TypeScript no
puede adivinar su tipo. El retorno, en cambio, es opcional —TypeScript también lo deduce del
return—, pero anotarlo es un contrato: si un día cambias el cuerpo y devuelves otra cosa
por error, el rojo salta en la propia función, no tres llamadas más allá donde se usa el
resultado.
Dejar que TypeScript infiera#
No hace falta anotar todo. Si declaras una variable con un valor, TypeScript deduce el tipo solo: es la inferencia. Anotar de más es ruido.
// TypeScript ya sabe que esto es string: no hace falta el ": string".
const nombre = "Mercy";
// Y que esto es number.
const partidas = 200;El tipo sigue ahí aunque no lo escribas: si intentas usar la variable como otra cosa, salta el error igual. La regla práctica: anota cuando TypeScript no puede adivinar (los parámetros de una función, o una variable sin valor inicial) y deja que infiera el resto.
Arrays tipados#
Un array tipado declara de qué son sus elementos: el tipo seguido de [].
// number[]: una lista de números.
const porSemana: number[] = [12, 8, 15, 20];
// string[]: una lista de textos.
const roles: string[] = ["Daño", "Tanque", "Apoyo"];A cambio, TypeScript vigila que no metas un intruso: porSemana.push("nueve") se marca en
rojo, porque "nueve" no es un number. Y como sabe que porSemana son números, dentro de
un reduce infiere que el acumulador también lo es, sin que anotes nada.
any y unknown: los dos comodines#
A veces no sabes (todavía) qué tipo tiene un valor: llega de un formulario, de una API, de fuera. Hay dos formas de decir “esto puede ser cualquier cosa”, y se comportan al revés:
anyapaga la comprobación. TypeScript deja de mirar ese valor y te deja hacer lo que sea con él. Es cómodo y peligroso: renuncias justo a la red de seguridad.unknowntambién acepta cualquier valor, pero no te deja usarlo hasta que compruebes de qué se trata. Es la puerta de entrada segura para datos externos.
// any: TypeScript no se queja, aunque la operación no tenga sentido.
const sucio: any = "no soy un número";
// unknown: TypeScript te frena hasta que compruebes qué es.
const seguro: unknown = "no soy un número";
// Esto da error con unknown (y por eso te protege):
// const doble = seguro * 2;Para usar un unknown, primero compruebas qué es. Con typeof, dentro del if TypeScript
ya lo trata como ese tipo y te deja operar sin error:
// dato podría ser cualquier cosa: es unknown.
const dato: unknown = 42;
// typeof comprueba el tipo en tiempo de ejecución.
if (typeof dato === "number") {
// Dentro del if, TypeScript ya sabe que dato es number: operar es seguro.
console.log(dato * 2);
}La regla: evita any; cuando un dato sea de verdad desconocido, usa unknown y
compruébalo antes de tocarlo. Esa comprobación es solo el aperitivo: el repertorio completo
para estrechar tipos lo verás en el capítulo de narrowing.
Comprueba lo que sabes#
Pregunta 1 de 5
¿Para qué sirve una anotación de tipo como `const n: number`?
Tu turno#
Tienes el motor de estadísticas del Team Builder a medio tipar. Complétalo en el editor: el panel “Problemas” te guía, y al darle a “Ejecutar” ves el resumen. Cuando lo tengas (o si te atascas), despliega las soluciones y fíjate en el salto de un nivel al siguiente.
Ejercicio · en esta página
Tipa el motor de estadísticas
Partes de un fichero con lógica que ya conoces (winrate, formato y un reduce) pero SIN anotar. Añade las anotaciones que faltan hasta que el panel "Problemas" quede vacío, y elige bien el tipo del dato externo.
Paso 1: Que funcione
- El panel "Problemas" queda vacío: no hay errores de tipo.
- Las funciones tienen sus parámetros anotados.
- Al pulsar "Ejecutar" se ve el resumen en la consola.
Paso 2: Que esté pulido
- No anotas lo que TypeScript ya infiere (no repites el tipo de una variable con valor inicial).
- Anotas las firmas de las funciones (parámetros y retorno).
- No usas any en ningún sitio.
Paso 3: Que sea excelente
- Infieres los valores locales pero anotas las firmas como contrato explícito.
- El dato externo es unknown (no any) y lo compruebas con typeof antes de usarlo: no lo dejas declarado sin más.
- Cero any en todo el fichero.
Ver soluciones
// Anotamos TODO de forma explícita: lo más directo cuando empiezas con tipos.
// Parámetros y retorno anotados a mano.
function winrate(victorias: number, partidas: number): number {
return victorias / partidas;
}
// Igual: parámetro y retorno explícitos.
function formatearPorcentaje(ratio: number): string {
return (ratio * 100).toFixed(1) + "%";
}
// Cada variable con su tipo escrito a mano.
const nombre: string = "Tracer";
const partidas: number = 120;
const victorias: number = 78;
// Array de números.
const porSemana: number[] = [12, 8, 15, 20];
// Lo dejamos como any para que "compile y ya": funciona, pero apaga la seguridad.
let datoExterno: any;
// reduce sobre el array de números.
const totalSemanas: number = porSemana.reduce(
(suma: number, n: number) => suma + n,
0,
);
// Pinta el resumen.
console.log(`Jugadora: ${nombre}`);
console.log(`Winrate: ${formatearPorcentaje(winrate(victorias, partidas))}`);
console.log(`Partidas últimas semanas: ${totalSemanas}`); Por qué este nivel
- Anota TODO de forma explícita, también las variables que TypeScript ya podría inferir: es la traducción literal de "ponle tipo a cada cosa".
- Compila sin errores y funciona, que es el primer requisito.
- Su límite: para "salir del paso" marca el dato externo como any, que apaga la red de seguridad justo donde más falta hace (datos de fuera).
// Anotamos SOLO donde aporta —las firmas de las funciones— y dejamos que
// TypeScript infiera el resto. Menos ruido, mismo tipado.
// La firma es el contrato de la función: ahí sí anotamos params y retorno.
function winrate(victorias: number, partidas: number): number {
return victorias / partidas;
}
function formatearPorcentaje(ratio: number): string {
return (ratio * 100).toFixed(1) + "%";
}
// Inferidas: el valor inicial ya fija el tipo (string, number, number).
const nombre = "Tracer";
const partidas = 120;
const victorias = 78;
// El literal [12, 8, 15, 20] ya se infiere como number[]: no hace falta anotarlo.
const porSemana = [12, 8, 15, 20];
// Nada de any: si el dato es desconocido, unknown no miente diciendo que vale todo.
let datoExterno: unknown;
// suma y n se infieren como number a partir de porSemana.
const totalSemanas = porSemana.reduce((suma, n) => suma + n, 0);
// Pinta el resumen.
console.log(`Jugadora: ${nombre}`);
console.log(`Winrate: ${formatearPorcentaje(winrate(victorias, partidas))}`);
console.log(`Partidas últimas semanas: ${totalSemanas}`); Por qué es mejor que el anterior
- Deja de anotar lo obvio: si una variable tiene valor inicial, TypeScript ya sabe su tipo. Menos ruido, misma seguridad.
- Mantiene la anotación donde sí aporta: la firma de cada función (sus parámetros y su retorno).
- Sustituye any por unknown: no miente diciendo que el dato externo "vale para todo".
// Equilibrio senior: dejamos que TypeScript infiera los valores locales, pero
// anotamos las FIRMAS de las funciones como contrato explícito. Así, si alguien
// cambia sin querer lo que devuelve una función, el error salta AQUÍ, en la
// función, y no a tres llamadas de distancia.
// Contrato explícito: recibe dos number, devuelve number.
function winrate(victorias: number, partidas: number): number {
return victorias / partidas;
}
// Contrato explícito: recibe number, devuelve string.
function formatearPorcentaje(ratio: number): string {
return (ratio * 100).toFixed(1) + "%";
}
// Locales: inferidos. El valor inicial ya fija el tipo, anotarlos sería ruido.
const nombre = "Tracer";
const partidas = 120;
const victorias = 78;
// Inferido como number[].
const porSemana = [12, 8, 15, 20];
// Dato externo (de un formulario o una API): entra como unknown, nunca como any.
// any apagaría la red de seguridad justo donde más falta hace: en datos de fuera.
const datoExterno: unknown = 5;
// Y aquí está el porqué de unknown frente a any: para USARLO hay que comprobar su
// tipo antes. typeof estrecha datoExterno a number dentro del if; si llegara un
// texto o un null, el if no entraría y no lo sumaríamos a ciegas.
let partidasExtra = 0;
if (typeof datoExterno === "number") {
// Dentro del if, datoExterno ya es number: sumarlo es seguro.
partidasExtra = datoExterno;
}
// suma y n se infieren como number; le añadimos las partidas externas ya validadas.
const totalSemanas = porSemana.reduce((suma, n) => suma + n, 0) + partidasExtra;
// Pinta el resumen.
console.log(`Jugadora: ${nombre}`);
console.log(`Winrate: ${formatearPorcentaje(winrate(victorias, partidas))}`);
console.log(`Partidas últimas semanas: ${totalSemanas}`); Por qué es mejor que el anterior
- Encuentra el equilibrio: infiere los valores locales pero anota las firmas como contrato. Si alguien cambia lo que devuelve una función, el error salta en la función, no a tres llamadas de distancia.
- No deja el dato externo como adorno: lo declara unknown y lo COMPRUEBA con typeof antes de usarlo. Ese es justo el valor de unknown frente a any: te deja usar el dato, pero solo después de verificar qué es (lo verás a fondo en narrowing).
- Cero any: la seguridad de tipos queda intacta de principio a fin.