TypeScript: Tipos básicos y el sistema de tipos

TypeScript: Tipos básicos y el sistema de tipos

Tabla de Contenido

Note

Segundo artículo de la serie sobre TypeScript. Recorreremos el alfabeto de tipos del lenguaje: desde any y unknown hasta uniones, intersecciones, tuplas y alias de tipo.

Cuando el typechecker sabe que un valor es de tipo T, no solo conoce su naturaleza: también sabe exactamente qué operaciones son válidas sobre ese valor y cuáles no. Ese es el propósito del sistema de tipos. Comprender el catálogo de tipos básicos en TypeScript es la base sobre la que se construye todo lo demás, así que dediquémosle la atención que merece.

Por qué importan los tipos: un ejemplo concreto

Los tipos transmiten intención de forma explícita y verificable. Comparemos la misma función en JavaScript y en TypeScript:

// JavaScript
function squareOf(n) {
  return n * n
}
squareOf(2)     // 4
squareOf('z')   // NaN — falla en silencio

// TypeScript
function squareOf(n: number) {
  return n * n
}
squareOf(2)     // 4
squareOf('z')   // Error TS2345: el argumento de tipo 'string' no es
                // asignable al parámetro de tipo 'number'.

En JavaScript, squareOf('z') devuelve NaN sin protestar y el error se propaga aguas abajo. En TypeScript el compilador lo intercepta en el sitio exacto donde se cometió.

any: la puerta de escape del sistema de tipos

any indica que una variable puede contener cualquier tipo de dato. Desactiva por completo la verificación:

let avenger: any = 123;
avenger = 'Dr. Strange';
console.log((avenger as string).charAt(0));

avenger = 15.23425;
console.log((<number>avenger).toFixed(2));

Cuando usas any, el typechecker deja de ayudarte. Puedes reorientarlo mediante un cast —indicar manualmente qué tipo debe asumir en un punto dado— con la sintaxis (variable as Tipo) o <Tipo>variable.

Warning

Evita any salvo en migraciones puntuales o fronteras con código sin tipado. Cada any es un agujero por el que se filtran errores que el compilador debería haber detenido.

unknown: la alternativa segura a any

unknown es el primo responsable de any. Acepta cualquier valor, pero TypeScript prohíbe operar sobre él hasta que acotes su tipo mediante una comprobación. Esto añade una capa de seguridad: sí admite operaciones de comparación e identidad (==, ===, ||, &&, el operador ternario ?) y negación con !, pero no el acceso a propiedades arbitrarias.

let dato: unknown = obtenerValor();

// dato.toFixed(2)        // Error: 'dato' es de tipo 'unknown'
if (typeof dato === 'number') {
  dato.toFixed(2)         // OK: dentro del guard, dato es number
}

Usa unknown siempre que recibas datos externos (respuestas de API, entrada del usuario) en lugar de any.

boolean

Acepta las operaciones ==, ===, ||, &&, el ternario ? y la negación !:

let a = true                // boolean
const c = true              // true (tipo literal)
let d: boolean = true       // boolean
let e: true = true          // true
let f: true = false         // Error TS2322: 'false' no es asignable a 'true'.

Info

El tipo true (literal) es más restrictivo que boolean: además de exigir un booleano, exige que su valor sea exactamente true. Los tipos literales son una herramienta poderosa para modelar estados precisos.

number y bigint

number cubre enteros, flotantes, Infinity, NaN y valores positivos y negativos:

let a = 1234                // number
const c = 5678              // 5678 (literal)
let e: number = 100         // number
let f: 26.218 = 26.218      // 26.218 (literal)
let g: 26.218 = 10          // Error TS2322

Un number representa con precisión hasta 2^53. Para enteros mayores existe bigint:

let a = 1234n               // bigint
const b = 5678n             // 5678n
let e = 88.5n               // Error TS1353: un literal bigint debe ser entero.
let h: bigint = 100         // Error TS2322: '100' no es asignable a 'bigint'.

string

let a = 'hello'             // string
const c = '!'              // '!' (literal)
let e: string = 'zoom'     // string
let f: 'john' = 'john'     // 'john' (literal)
let g: 'john' = 'zoe'      // Error TS2322

symbol

Un symbol representa un identificador único. Se crea con Symbol() y admite una descripción opcional útil para depurar:

let id = Symbol("id");      // symbol con descripción "id"

La garantía clave: dos símbolos nunca son iguales, aunque compartan descripción. La descripción es solo una etiqueta sin efecto sobre la identidad.

let id1 = Symbol("id");
let id2 = Symbol("id");
console.log(id1 === id2);   // false

let d = id1 + 'x'           // Error TS2469: el operador '+' no aplica a 'symbol'.

Objetos

Hay dos formas de tipar objetos. La primera usa la palabra clave object, que solo garantiza que el valor no es null pero no expone sus propiedades, porque TypeScript no sabe qué objeto es:

let a: object = { b: 'x' }
// a.b   // Error: la propiedad 'b' no existe en el tipo 'object'

La segunda —y recomendada— describe la forma exacta del objeto:

let c: {
  firstName: string
  lastName: string
} = {
  firstName: 'john',
  lastName: 'barrowman'
}

Las definiciones de objeto admiten campos opcionales (?), índices dinámicos y propiedades de solo lectura:

let a: {
  b: number          // obligatorio, number
  c?: string         // opcional, string
  [key: number]: boolean   // cualquier clave numérica → boolean
}
let user: {
  readonly firstName: string
} = { firstName: 'abby' }

user.firstName = 'abbey'   // Error TS2540: propiedad de solo lectura.

Tip

Evita tipar con el objeto vacío {}. Acepta cualquier valor distinto de null o undefined (incluso números o arreglos), lo que vacía de sentido la anotación.

Alias de tipo

Los alias asignan un nombre semántico a un tipo, mejorando la legibilidad sin alterar la verificación:

type Age = number

type Person = {
  name: string
  age: Age
}

let driver: Person = {
  name: 'James May',
  age: 55
}

Como Age es solo un alias de number, sigue siendo compatible con cualquier number. Ten presente que un alias solo puede declararse una vez por ámbito; aplica las mismas reglas de shadowing que let, const y var.

Tipos unión e intersección

Recurriendo a la teoría de conjuntos: la unión (|) admite uno u otro tipo —o ambos—, mientras que la intersección (&) exige cumplir simultáneamente todas las formas combinadas.

flowchart LR
    subgraph Union["A | B"]
        U[Cualquier valor que sea A, B o ambos]
    end
    subgraph Inter["A & B"]
        I[Solo valores que cumplen A y B a la vez]
    end
type Cat = { name: string, purrs: boolean }
type Dog = { name: string, barks: boolean, wags: boolean }

type CatOrDogOrBoth = Cat | Dog     // unión
type CatAndDog = Cat & Dog          // intersección

Un valor Cat | Dog puede ser un gato, un perro o un objeto que cumpla ambas formas. Un valor Cat & Dog, en cambio, está obligado a tener name, purrs, barks y wags al mismo tiempo.

Arreglos

let a = [1, 2, 3]           // number[]
let d = [1, 'a']            // (string | number)[]
let h: number[] = []
h.push(1)                   // OK
h.push('red')               // Error TS2345

Un arreglo declarado vacío sin anotación arranca como any[] y va ampliando su tipo según lo que insertes —otra razón para anotar explícitamente las colecciones en las fronteras del sistema.

Tuplas

Las tuplas son un subconjunto de los arreglos con longitud fija y tipos posicionales:

let a: [number] = [1]

// [nombre, apellido, año de nacimiento]
let b: [string, string, number] = ['malcolm', 'gladwell', 1963]

b = ['queen', 'elizabeth', 'ii', 1926]  // Error TS2322: posición/longitud inválida

También admiten posiciones opcionales:

// Tarifas de tren que a veces varían según la dirección
let trainFares: [number, number?][] = [
  [3.75],
  [8.25, 7.70],
  [10.50]
]

Conclusión

El sistema de tipos de TypeScript va mucho más allá de etiquetar variables: distingue entre any (inseguro) y unknown (seguro), modela estados exactos con tipos literales, compone formas mediante uniones e intersecciones y garantiza estructuras rígidas con tuplas. Dominar este catálogo es el prerrequisito para los temas avanzados de la serie.

En el siguiente artículo aplicaremos estos fundamentos a los objetos y tipos personalizados, donde los alias de tipo se vuelven el centro del diseño.

Etiquetas :