Saltar al contenido
tipos-basicos.ts · devschool

Tipos básicos en TypeScript

Lección 3 de 15 · 11 min de lectura · Actualizado el

En esta lección
  1. Anotaciones de tipo
  2. Inferencia: cuándo no hace falta anotar
  3. Los tipos primitivos: string, number y boolean
  4. null y undefined
  5. any: apagar las comprobaciones
  6. unknown: “no sé qué es, compruébalo”
  7. void y never
  8. Tipos literales
  9. Aserciones de tipo con as
  10. Objetos simples
  11. Errores frecuentes
  12. Resumen

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ésSí
Parámetros de funcionesSí, siempre
Valor de retorno de una funciónOpcional; ú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):

TipoValoresEjemplo
stringTextos, con cualquier tipo de comillas"hola", 'hola', `hola ${n}`
numberEnteros y decimales, también NaN e Infinity42, 3.5, -7
booleanVerdadero o falsotrue, false
bigintEnteros enormes9007199254740993n
symbolIdentificadores únicosSymbol("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: any tiene 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, usa unknown.

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
anyunknown
Acepta cualquier valorSíSí
Se puede usar sin comprobarSíNo
SeguridadNingunaTotal
CuándoMigraciones, casos muy concretosDatos 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 as lo 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 String o Number con mayúscula: son los objetos envoltorio. Los tipos son string, number y boolean.
  • Anotar todo: const nombre: string = "Ana" es redundante. Deja que la inferencia trabaje y anota parámetros y datos externos.
  • Recurrir a any para quitar un error: el error sigue ahí, solo lo escondes hasta que el programa falle. Usa unknown y comprueba.
  • Confiar en as para datos externos: una aserción no convierte ni valida nada. "5" as unknown as number sigue siendo el texto "5" al ejecutar.
  • Asignar null a una variable string: con strict, no se puede. Declara string | null si el valor puede faltar.
  • Confundir void con undefined: void se usa como tipo de retorno de funciones que no devuelven nada; no lo uses para variables.

Resumen

ConceptoLo esencial
Anotaciónlet x: number = 5
Inferencialet x = 5 ya es number; anota parámetros y datos externos
Primitivosstring, number, boolean (y bigint, symbol)
null / undefinedTipos propios; usa string | null si un valor puede faltar
anyDesactiva comprobaciones; evítalo
unknownAcepta todo, pero obliga a comprobar antes de usar
voidFunción que no devuelve nada
neverAlgo que nunca ocurre (una función que siempre lanza un error)
Literales"oscuro", "S" | "M" | "L"; const infiere literales
as constObjeto o array de solo lectura con tipos literales
as TipoAserció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

[TypeScript] ¿Por qué da error la última línea?
let autor = "Patrick Rothfuss";
autor = 42;

[TypeScript] ¿Qué ocurre con esta línea?
let entrada: unknown = "  Ana  ";
console.log(entrada.trim());

[TypeScript] ¿Qué tipo infiere TypeScript para tema y para modo?
const tema = "oscuro";
let modo = "oscuro";

[TypeScript] ¿Qué pasa al ejecutar esto, si el JSON no incluye paginas?
const texto = '{"titulo": "Dune"}';
const libro = JSON.parse(texto) as { titulo: string; paginas: number };
console.log(libro.paginas.toFixed(0));

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.