Tipado de Colecciones y Estructuras de Datos Avanzadas en TypeScript: Maps, Sets y Tuplas
Este tutorial profundiza en el tipado de estructuras de datos avanzadas como Maps, Sets y Tuplas en TypeScript. Aprenderás a declarar y manipular estas colecciones con seguridad de tipos, mejorando la robustez y legibilidad de tu código.
🚀 Introducción al Tipado de Colecciones Avanzadas
TypeScript nos permite escribir código más robusto y predecible al agregar un sistema de tipos estático a JavaScript. Si bien es común tipar arrays y objetos, estructuras de datos como Map, Set y las Tuplas (cuando no son simples arrays) a menudo se pasan por alto o se tipan incorrectamente. Dominar su tipado es crucial para desarrollar aplicaciones complejas y mantener la seguridad de tipos en todo tu proyecto.
En este tutorial, exploraremos en detalle cómo tipar estas colecciones avanzadas, proporcionando ejemplos prácticos y las mejores prácticas para integrarlas en tus proyectos TypeScript. ¡Prepárate para llevar tu conocimiento de tipos a un nuevo nivel! ✨
📌 ¿Por qué tipar Maps, Sets y Tuplas?
La principal razón para tipar estas estructuras de datos es la seguridad y mantenibilidad del código. Sin un tipado adecuado, el compilador de TypeScript no puede detectar errores relacionados con el tipo de los elementos almacenados o la estructura de las tuplas, lo que puede llevar a errores en tiempo de ejecución.
🗺️ Tipado de la Clase Map en TypeScript
La clase Map de JavaScript es una colección de pares clave-valor donde las claves pueden ser de cualquier tipo (no solo cadenas como en los objetos). TypeScript ofrece una forma directa de tipar Maps utilizando genéricos.
Declaración Básica de Map
Para tipar un Map, especificas el tipo de la clave y el tipo del valor entre corchetes angulares <KeyType, ValueType>.
// Map donde las claves son números y los valores son cadenas
const usuariosById: Map<number, string> = new Map();
usuariosById.set(1, "Alice");
usuariosById.set(2, "Bob");
// usuariosById.set("3", "Charlie"); // Error: Argumento de tipo 'string' no asignable a parámetro de tipo 'number'.
usuariosById.set(3, "Charlie");
console.log(usuariosById.get(1)); // "Alice"
Usando Tipos de Objeto como Claves o Valores
Map es muy útil cuando necesitas usar objetos como claves.
interface Usuario {
id: number;
nombre: string;
}
interface Perfil {
edad: number;
ciudad: string;
}
// Map donde las claves son objetos Usuario y los valores son objetos Perfil
const perfilesDeUsuario: Map<Usuario, Perfil> = new Map();
const usuario1: Usuario = { id: 1, nombre: "David" };
const usuario2: Usuario = { id: 2, nombre: "Eva" };
perfilesDeUsuario.set(usuario1, { edad: 30, ciudad: "Madrid" });
perfilesDeUsuario.set(usuario2, { edad: 25, ciudad: "Barcelona" });
console.log(perfilesDeUsuario.get(usuario1)); // { edad: 30, ciudad: 'Madrid' }
Métodos de Map con Tipado
TypeScript tipa automáticamente los métodos de Map (como set, get, has, delete, keys, values, entries) basándose en los tipos que declaraste.
const productosStock: Map<string, number> = new Map();
productosStock.set("Manzanas", 100);
productosStock.set("Peras", 75);
// Iterando sobre claves y valores
for (const nombre of productosStock.keys()) {
console.log(`Producto: ${nombre}`); // nombre es de tipo 'string'
}
for (const cantidad of productosStock.values()) {
console.log(`Cantidad: ${cantidad}`); // cantidad es de tipo 'number'
}
for (const [nombre, cantidad] of productosStock.entries()) {
console.log(`${nombre}: ${cantidad}`); // nombre es 'string', cantidad es 'number'
}
productosStock.forEach((cantidad, nombre) => {
console.log(`${nombre} tiene ${cantidad} unidades.`); // 'cantidad' es number, 'nombre' es string
});
Construyendo un Map a partir de un Array
También puedes inicializar un Map a partir de un array de pares [key, value]. Es importante que este array también esté correctamente tipado.
type Coordinador = [string, { telefono: string; email: string }];
const listaCoordinadores: Coordinador[] = [
["Ana", { telefono: "123-456-789", email: "ana@example.com" }],
["Beto", { telefono: "987-654-321", email: "beto@example.com" }],
];
const coordinadores: Map<string, { telefono: string; email: string }> = new Map(listaCoordinadores);
console.log(coordinadores.get("Ana"));
🌿 Tipado de la Clase Set en TypeScript
La clase Set es una colección de valores únicos de cualquier tipo. TypeScript la tipa de manera similar a Map, usando un solo tipo genérico para los elementos que contiene.
Declaración Básica de Set
Para tipar un Set, especificas el tipo de los elementos que contendrá entre corchetes angulares <ElementType>.
// Set de números únicos
const idsUnicos: Set<number> = new Set();
idsUnicos.add(10);
idsUnicos.add(20);
idsUnicos.add(10); // No se agrega, ya existe
// idsUnicos.add("30"); // Error: Argumento de tipo 'string' no asignable a parámetro de tipo 'number'.
idsUnicos.add(30);
console.log(idsUnicos.size); // 3
Usando Tipos de Objeto en un Set
Al igual que con Map, puedes almacenar objetos en un Set.
interface Tag {
id: string;
nombre: string;
}
const tagA: Tag = { id: "ts", nombre: "TypeScript" };
const tagB: Tag = { id: "js", nombre: "JavaScript" };
const tagC: Tag = { id: "ts", nombre: "TypeScript" }; // Mismo valor, diferente referencia
// Set de objetos Tag
const tagsUnicos: Set<Tag> = new Set();
tagsUnicos.add(tagA);
tagsUnicos.add(tagB);
tagsUnicos.add(tagC); // Se agregará porque es una referencia de objeto diferente a tagA
console.log(tagsUnicos.size); // 3 (si tagA y tagC son objetos diferentes en memoria)
// Si quisiéramos que fueran 'únicos' por id, necesitaríamos una lógica personalizada o usar un Map.
Métodos de Set con Tipado
Los métodos de Set como add, has, delete e forEach también se benefician del tipado genérico.
const permisosUsuario: Set<string> = new Set();
permisosUsuario.add("admin");
permisosUsuario.add("editor");
if (permisosUsuario.has("admin")) {
console.log("El usuario tiene permiso de administrador.");
}
// Iterando sobre los elementos
for (const permiso of permisosUsuario) {
console.log(`Permiso: ${permiso}`); // permiso es de tipo 'string'
}
permisosUsuario.forEach(permiso => {
console.log(`Permiso encontrado: ${permiso}`); // 'permiso' es string
});
Inicialización de Set a partir de un Array
const coloresFavoritos: string[] = ["rojo", "azul", "verde", "azul"];
const conjuntoColores: Set<string> = new Set(coloresFavoritos);
console.log(conjuntoColores); // Set { 'rojo', 'azul', 'verde' }
🤝 Tipado de Tuplas en TypeScript
Las tuplas en TypeScript son arrays con un número fijo de elementos donde cada elemento puede tener un tipo diferente y conocido. Son muy útiles para representar registros de datos con una estructura específica.
Declaración Básica de Tuplas
Las tuplas se declaran especificando los tipos de sus elementos en un array literal de tipos [Type1, Type2, ..., TypeN].
// Tupla para representar un punto 2D (x, y)
let punto2D: [number, number];
punto2D = [10, 20];
// punto2D = [10, "20"]; // Error: Argumento de tipo 'string' no asignable a parámetro de tipo 'number'.
// punto2D = [10, 20, 30]; // Error: El tipo '[number, number, number]' no es asignable al tipo '[number, number]'.
console.log(`X: ${punto2D[0]}, Y: ${punto2D[1]}`);
// Tupla para representar información de usuario (id, nombre, activo)
let usuarioInfo: [number, string, boolean];
usuarioInfo = [1, "Alice", true];
Tuplas con Nombres de Elementos (TypeScript 4.0+)
Para mejorar la legibilidad, TypeScript 4.0 introdujo la capacidad de nombrar los elementos de una tupla. Esto no cambia el tipo, pero actúa como documentación.
// Tupla con nombres para los elementos
type Coordenada3D = [x: number, y: number, z: number];
const posicion: Coordenada3D = [1, 2, 3];
console.log(`Posición Z: ${posicion[2]}`);
// Acceso sigue siendo por índice
// console.log(posicion.z); // Error: La propiedad 'z' no existe en el tipo 'Coordenada3D'.
Tuplas con Elementos Opcionales y Rest Parameters
TypeScript también soporta elementos opcionales (?) y rest parameters (...) en las tuplas, lo que las hace aún más flexibles.
// Tupla con un elemento opcional
type RespuestaAPI = [status: number, data?: object, error?: string];
const exito: RespuestaAPI = [200, { mensaje: "OK" }];
const fallo: RespuestaAPI = [500, undefined, "Error del servidor"];
const soloStatus: RespuestaAPI = [204];
console.log(exito[1]);
// Tupla con un rest parameter
type LogEntry = [timestamp: number, level: string, ...mensajes: string[]];
const log1: LogEntry = [Date.now(), "INFO", "Aplicación iniciada"];
const log2: LogEntry = [Date.now(), "ERROR", "Archivo no encontrado", "Ruta: /src/app.ts"];
console.log(log2[2]); // "Archivo no encontrado"
console.log(log2[3]); // "Ruta: /src/app.ts"
Desestructuración de Tuplas
La desestructuración de tuplas es una característica muy poderosa, y TypeScript asegura que los tipos se mantengan correctos.
type Persona = [nombre: string, edad: number, esEstudiante: boolean];
const david: Persona = ["David", 30, false];
const [nombre, edad, esEstudiante] = david;
console.log(`${nombre} tiene ${edad} años.`); // nombre es string, edad es number
// const [nombre, edad, esEstudiante, ciudad] = david; // Error: La longitud de la tupla '[string, number, boolean]' en el índice '3' es demasiado corta.
🔄 Casos de Uso Comunes y Mejores Prácticas
Cuando usar Map vs. Objeto Record
| Característica | Map<KeyType, ValueType> | Record<KeyType, ValueType> |
|---|---|---|
| --- | --- | --- |
| Tipo de Clave | Cualquier tipo (objeto, función, primitivo) | Generalmente string o number (literales) |
| Orden de Inserción | Mantiene el orden de inserción | No garantiza el orden de inserción |
| --- | --- | --- |
| Rendimiento | Mejor rendimiento con muchas entradas o claves no-string | Óptimo para objetos pequeños con claves string estáticas |
| APIs | Métodos específicos (set, get, has, size, forEach, keys, values, entries) | Acceso directo por propiedad (obj.key, obj['key']), Object.keys, Object.values |
| --- | --- | --- |
| Uso Típico | Caches, colecciones donde las claves son objetos, diccionarios dinámicos | Configuración, DTOs, objetos simples |
Cuando usar Set vs. Array con Unicidad Manual
Set es ideal cuando la unicidad de los elementos es una preocupación principal y la performance en la búsqueda o adición es crítica.
// Con Set
const invitados: Set<string> = new Set();
invitados.add("Ana");
invitados.add("Juan");
invitados.add("Ana"); // Ignorado
console.log(invitados.has("Juan")); // true
// Con Array (requiere lógica manual para unicidad)
const asistentes: string[] = [];
function addAsistente(nombre: string) {
if (!asistentes.includes(nombre)) {
asistentes.push(nombre);
}
}
addAsistente("Maria");
addAsistente("Pedro");
addAsistente("Maria");
console.log(asistentes.includes("Pedro")); // true
Uso Efectivo de Tuplas
Las tuplas son excelentes para:
- Retorno de múltiples valores de una función: Cuando una función necesita devolver varios valores relacionados que tienen diferentes tipos.
function obtenerCoordenadas(): [number, number] {
return [100, 200];
}
const [x, y] = obtenerCoordenadas();
console.log(x, y);
- Representar pares clave-valor fijos: Aunque
Mapse usa para colecciones, una tupla puede representar un único par clave-valor fijo.
type ParConfig = [key: string, value: string | number | boolean];
const configuracion: ParConfig = ["theme", "dark"];
- Registros de base de datos o CSV: Cuando cada fila tiene una estructura predefinida de tipos.
🛠️ Herramientas y Patrones Avanzados
ReadonlyMap y ReadonlySet
Para colecciones inmutables, TypeScript proporciona los tipos ReadonlyMap y ReadonlySet. Estos tipos eliminan los métodos que mutan la colección (set, add, delete, clear), asegurando que la colección no pueda ser modificada después de su creación.
const precios: ReadonlyMap<string, number> = new Map([["Leche", 1.5], ["Pan", 2.0]]);
// precios.set("Agua", 1.0); // Error: La propiedad 'set' no existe en el tipo 'ReadonlyMap<string, number>'.
const permisosDefecto: ReadonlySet<string> = new Set(["ver", "editar"]);
// permisosDefecto.add("eliminar"); // Error: La propiedad 'add' no existe en el tipo 'ReadonlySet<string>'.
¿Por qué usar `ReadonlyMap` / `ReadonlySet`?
Ayudan a enforce la inmutabilidad, lo cual es beneficioso en arquitecturas como Redux o React con estados complejos, donde evitar mutaciones accidentales es crucial. También mejora la inferencia de que una colección no cambiará durante su ciclo de vida.Transformando Colecciones Tipadas
Es común necesitar transformar una colección en otra. TypeScript te ayuda a mantener los tipos correctos durante estas operaciones.
// De Map a Array de Tuplas
const ciudadesPoblacion: Map<string, number> = new Map([
["Tokyo", 37_000_000],
["Delhi", 32_000_000]
]);
const arrayCiudades: Array<[string, number]> = Array.from(ciudadesPoblacion.entries());
console.log(arrayCiudades); // [ [ 'Tokyo', 37000000 ], [ 'Delhi', 32000000 ] ]
// De Set a Array
const frutasFavoritas: Set<string> = new Set(["manzana", "banana", "cereza"]);
const arrayFrutas: string[] = Array.from(frutasFavoritas);
console.log(arrayFrutas); // [ 'manzana', 'banana', 'cereza' ]
Diagrama de Flujo: Elegir la Colección Adecuada
Este diagrama te ayudará a decidir qué estructura de datos utilizar en función de tus necesidades:
✅ Conclusión
Dominar el tipado de Map, Set y Tuplas en TypeScript es un paso crucial para escribir código más seguro, legible y mantenible. Al aplicar los genéricos y las características específicas de las tuplas, puedes asegurarte de que tus colecciones se comporten como esperas y detectar errores de tipo en tiempo de compilación, no en tiempo de ejecución.
Esperamos que esta guía te haya proporcionado las herramientas y el conocimiento necesario para integrar estas poderosas estructuras de datos tipadas en tus proyectos TypeScript. ¡Feliz tipado! 🥳
Tutoriales relacionados
- Tipado de Genéricos en Funciones y Clases con TypeScript: Flexibilidad y Seguridadintermediate15 min
- Tipos Utilitarios en TypeScript: Potenciando Tu Código con Mapped Types y Condicionalesadvanced18 min
- Tipado Preciso de APIs REST con TypeScript: Generación de Cliente y Schemasintermediate20 min
- Tipado de Datos Immutables con Immer y TypeScript: Construyendo Aplicaciones Robustasintermediate15 min
- Dominando los Decoradores en TypeScript: Una Guía Práctica con Ejemplos Realesintermediate15 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!