Tipos básicos en TypeScript
En esta lección
Un tipo describe qué valores puede guardar una variable y qué se puede hacer con ellos: con un string puedes llamar a toUpperCase(), con un number puedes multiplicar. En esta lección aprenderás a escribir tipos (anotaciones), a dejar que TypeScript los deduzca (inferencia) y conocerás los tipos básicos del lenguaje, incluidos algunos especiales como any, unknown, void y never. Es la base de todo el curso.
Anotaciones de tipo
Una anotación de tipo se escribe con dos puntos después del nombre de la variable:
let titulo: string = "El nombre del viento";
let paginas: number = 662;
let prestado: boolean = false;
console.log(titulo, paginas, prestado);
// El nombre del viento 662 false
A partir de aquí, TypeScript vigila que cada variable guarde siempre valores de su tipo:
let paginas: number = 662;
paginas = "seiscientas";
// Error: Type 'string' is not assignable to type 'number'.
Este es el mensaje que más vas a ver: Type X is not assignable to type Y, “el tipo X no se puede asignar al tipo Y”. Significa que intentas meter un valor donde no encaja.
Inferencia: cuándo no hace falta anotar
En la mayoría de casos no tienes que escribir el tipo, porque TypeScript lo infiere (lo deduce) del valor inicial:
let autor = "Patrick Rothfuss"; // TypeScript deduce string
let ejemplares = 4; // number
ejemplares = ejemplares - 1; // bien
autor = 42;
// Error: Type 'number' is not assignable to type 'string'.
La protección es exactamente la misma que con anotación. En VS Code puedes pasar el ratón sobre cualquier variable para ver el tipo deducido.
¿Cuándo conviene anotar, entonces? Estas son las reglas prácticas:
| Situación | ¿Anotar? |
|---|---|
Variable con valor inicial (let total = 0) | No, se infiere |
| Variable que declaras vacía y rellenas después | Sí |
| Parámetros de funciones | Sí, siempre |
| Valor de retorno de una función | Opcional; útil en funciones importantes |
Datos que vienen de fuera (JSON.parse, APIs) | Sí, con cuidado |
Una variable declarada sin valor y sin tipo es un caso especial: TypeScript va siguiendo lo que le asignas, pero es más claro decirlo tú:
let mensaje: string;
const hora = 21;
if (hora < 14) {
mensaje = "Buenos días";
} else {
mensaje = "Buenas tardes";
}
console.log(mensaje);
// Buenas tardes
Consejo: deja que TypeScript infiera todo lo que pueda. Anotar lo evidente (
let total: number = 0) no aporta nada y llena el código de ruido. Donde los tipos sí importan es en los “bordes”: parámetros de funciones y datos externos.
Los tipos primitivos: string, number y boolean
Son los mismos tipos primitivos de JavaScript que ya conoces (repásalos en Tipos de datos):
| Tipo | Valores | Ejemplo |
|---|---|---|
string | Textos, con cualquier tipo de comillas | "hola", 'hola', `hola ${n}` |
number | Enteros y decimales, también NaN e Infinity | 42, 3.5, -7 |
boolean | Verdadero o falso | true, false |
bigint | Enteros enormes | 9007199254740993n |
symbol | Identificadores únicos | Symbol("id") |
En TypeScript no hay un tipo distinto para enteros y decimales: todo es number.
const precio: number = 12.5;
const unidades: number = 3;
const descripcion: string = `${unidades} cuadernos`;
const hayStock: boolean = unidades > 0;
console.log(descripcion, precio * unidades, hayStock);
// 3 cuadernos 37.5 true
Cuidado: escribe los tipos en minúscula:
string,number,boolean. Con mayúscula (String,Number) son los objetos envoltorio de JavaScript, algo distinto que casi nunca querrás usar.
null y undefined
Con strict activado, null y undefined son tipos propios y no se pueden asignar a cualquier variable:
let socio: string = "Iker";
socio = null;
// Error: Type 'null' is not assignable to type 'string'.
Esto es una gran ventaja: si una variable es string, tienes la garantía de que hay un texto dentro y no te encontrarás un null por sorpresa. Si un valor puede faltar, lo indicas explícitamente con | (“o”):
let socioDelMes: string | null = null;
console.log(socioDelMes);
socioDelMes = "Iker";
console.log(socioDelMes);
// null
// Iker
Esto se llama tipo unión y lo verás a fondo en Uniones y alias.
any: apagar las comprobaciones
any significa “cualquier cosa”: una variable any acepta cualquier valor y permite hacer cualquier operación sin que TypeScript compruebe nada.
let dato: any = 4;
dato = "cuatro";
dato = { valor: 4 };
console.log(dato.valor.toUpperCase());
// TypeError: dato.valor.toUpperCase is not a function
Compila sin un solo error y falla al ejecutar, exactamente como JavaScript. Con any pierdes todas las ventajas de TypeScript, y además se “contagia”: lo que sacas de un any también es any.
Con strict, TypeScript no permite que algo sea any sin que lo digas. Si dejas un parámetro sin tipo, te avisa:
function duplicar(cantidad) {
return cantidad * 2;
}
// Error: Parameter 'cantidad' implicitly has an 'any' type.
Nota:
anytiene un uso legítimo: migrar poco a poco un proyecto de JavaScript a TypeScript. Fuera de eso, evítalo. Si no sabes qué tipo tiene algo, usaunknown.
unknown: “no sé qué es, compruébalo”
unknown también acepta cualquier valor, pero no te deja usarlo hasta que compruebes qué es. Es la versión segura de any:
let entrada: unknown = " Ana ";
console.log(entrada.trim());
// Error: 'entrada' is of type 'unknown'.
Para usarlo, primero compruebas el tipo con typeof. Dentro del if, TypeScript ya sabe que es un string:
let entrada: unknown = " Ana ";
if (typeof entrada === "string") {
console.log(entrada.trim());
} else {
console.log("No es un texto");
}
// Ana
El caso típico es JSON.parse, que devuelve any. Guardar su resultado como unknown te obliga a comprobar los datos antes de fiarte de ellos:
const texto = '{"plazas": 12}';
const datos: unknown = JSON.parse(texto);
if (typeof datos === "object" && datos !== null && "plazas" in datos) {
console.log("Plazas libres:", datos.plazas);
}
// Plazas libres: 12
any | unknown | |
|---|---|---|
| Acepta cualquier valor | Sí | Sí |
| Se puede usar sin comprobar | Sí | No |
| Seguridad | Ninguna | Total |
| Cuándo | Migraciones, casos muy concretos | Datos externos o de tipo desconocido |
void y never
void indica que una función no devuelve nada útil. Se usa en el tipo de retorno:
function avisar(mensaje: string): void {
console.log(`[Aviso] ${mensaje}`);
}
avisar("La biblioteca cierra a las 20:00");
// [Aviso] La biblioteca cierra a las 20:00
never es el tipo de algo que nunca puede ocurrir. Por ejemplo, una función que siempre lanza un error nunca llega a devolver nada:
function fallar(motivo: string): never {
throw new Error(motivo);
}
try {
fallar("Libro no encontrado");
} catch (e) {
console.log("Capturado:", (e as Error).message);
}
// Capturado: Libro no encontrado
never parece raro ahora, pero es muy útil para comprobar que has tratado todos los casos posibles de un switch. Lo verás en Estrechar tipos.
Tipos literales
Un tipo literal es un tipo que solo admite un valor concreto. Lo verás al declarar con const:
let modo = "oscuro"; // tipo string: con let puede cambiar
const tema = "oscuro"; // tipo "oscuro": con const nunca cambia
Como una constante no puede cambiar, TypeScript le da el tipo más preciso posible: no “un texto cualquiera”, sino exactamente "oscuro". Si pasas el ratón por encima en VS Code verás const tema: "oscuro".
Un literal solo es poco útil, pero combinado con | es una herramienta muy potente para limitar los valores permitidos:
let talla: "S" | "M" | "L" = "M";
talla = "XL";
// Error: Type '"XL"' is not assignable to type '"S" | "M" | "L"'.
as const
Con los objetos y arrays pasa algo parecido: aunque los declares con const, sus propiedades se pueden cambiar, así que TypeScript infiere tipos generales (string, number). Si añades as const, todo se vuelve de solo lectura y con tipos literales:
const horario = { apertura: 9, cierre: 21 } as const;
// tipo: { readonly apertura: 9; readonly cierre: 21 }
horario.cierre = 22;
// Error: Cannot assign to 'cierre' because it is a read-only property.
Es útil para valores de configuración que no deben cambiar nunca. Lo usarás con arrays en Arrays y tuplas.
Aserciones de tipo con as
A veces sabes más que TypeScript sobre un valor. Una aserción de tipo (valor as Tipo) le dice al compilador: “confía en mí, esto es de este tipo”. El ejemplo clásico es el DOM:
const campo = document.querySelector("#email") as HTMLInputElement;
// no-check: necesita una página con #email
querySelector no puede saber qué elemento encontrará, así que devuelve un tipo general. Con as HTMLInputElement le dices que es un <input> y ya puedes usar campo.value. Lo verás en TypeScript y el DOM.
El problema es que una aserción no comprueba nada: si te equivocas, TypeScript te cree igualmente y el error salta al ejecutar.
const respuesta = '{"titulo": "Dune"}';
const libro = JSON.parse(respuesta) as { titulo: string; paginas: number };
console.log(libro.paginas.toFixed(0));
// TypeError: Cannot read properties of undefined (reading 'toFixed')
TypeScript no se queja porque le has asegurado que existe paginas, pero el JSON no la trae. Solo bloquea las aserciones claramente imposibles:
const codigo = "B-204" as number;
// Error: Conversion of type 'string' to type 'number' may be a mistake because
// neither type sufficiently overlaps with the other.
Cuidado: usa
aslo menos posible. Cada aserción es un punto donde apagas la comprobación de tipos. Si puedes resolverlo con una comprobación (typeof,if), es mejor.
Objetos simples
Para tipar un objeto se escribe su forma entre llaves, con el tipo de cada propiedad:
let libro: { titulo: string; paginas: number; prestado: boolean } = {
titulo: "Dune",
paginas: 704,
prestado: true,
};
console.log(`${libro.titulo} (${libro.paginas} págs.)`);
// Dune (704 págs.)
Si falta una propiedad o sobra alguna, TypeScript avisa. Lo normal es dar un nombre a esas formas con interface o type para reutilizarlas; lo verás en Objetos e interfaces.
Errores frecuentes
- Usar
StringoNumbercon mayúscula: son los objetos envoltorio. Los tipos sonstring,numberyboolean. - Anotar todo:
const nombre: string = "Ana"es redundante. Deja que la inferencia trabaje y anota parámetros y datos externos. - Recurrir a
anypara quitar un error: el error sigue ahí, solo lo escondes hasta que el programa falle. Usaunknowny comprueba. - Confiar en
aspara datos externos: una aserción no convierte ni valida nada."5" as unknown as numbersigue siendo el texto"5"al ejecutar. - Asignar
nulla una variablestring: constrict, no se puede. Declarastring | nullsi el valor puede faltar. - Confundir
voidconundefined:voidse usa como tipo de retorno de funciones que no devuelven nada; no lo uses para variables.
Resumen
| Concepto | Lo esencial |
|---|---|
| Anotación | let x: number = 5 |
| Inferencia | let x = 5 ya es number; anota parámetros y datos externos |
| Primitivos | string, number, boolean (y bigint, symbol) |
null / undefined | Tipos propios; usa string | null si un valor puede faltar |
any | Desactiva comprobaciones; evítalo |
unknown | Acepta todo, pero obliga a comprobar antes de usar |
void | Función que no devuelve nada |
never | Algo que nunca ocurre (una función que siempre lanza un error) |
| Literales | "oscuro", "S" | "M" | "L"; const infiere literales |
as const | Objeto o array de solo lectura con tipos literales |
as Tipo | Aserción: confías en ti, no comprueba nada |
En la siguiente lección verás cómo tipar listas de datos: Arrays y tuplas.
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.