🇺🇸 EN
TypeScript
·6 min read·↗·🇺🇸 EN

Cómo usar el tipo Record en TypeScript

Portada principal del artículo: Cómo usar el tipo Record en TypeScript

Cuando construimos aplicaciones en JavaScript, con frecuencia guardamos datos en diccionarios de clave-valor: mapas de configuración, catálogos de estados y tablas de caché.

En JavaScript puro, cualquier clave se puede agregar, escribir con errores o leer sin ninguna advertencia:

const userRoles = {
  admin: 'Full Access',
  editor: 'Edit Content',
};
 
// ¡Una clave mal escrita devuelve undefined silenciosamente!
console.log(userRoles.admn);

¿Cómo logramos que estos objetos de clave-valor tengan tipos estrictos, sean predecibles y estén bien documentados en TypeScript?

La respuesta es el tipo utilitario integrado Record<Keys, Type> de TypeScript. En este post, te mostraré cómo funciona Record, cómo usarlo con tipos union y cuándo conviene elegir Record frente a los index signatures y a Map.


1. ¿Qué es el tipo utilitario Record<Keys, Type>?

El tipo utilitario Record<Keys, Type> construye un tipo de objeto donde:

  • Las claves deben coincidir con el tipo Keys (puede ser string, number, symbol o una unión de literales).
  • Los valores deben coincidir con el tipo Type.

Por dentro, TypeScript define Record usando un tipo mapeado (mapped type):

type Record<K extends keyof any, T> = {
  [P in K]: T;
};

Le estás diciendo a TypeScript: "Quiero un objeto donde cada propiedad pertenezca a K, y cada valor sea de tipo T."

Veamos cómo funciona esto con un ejemplo sencillo.


2. Ejemplo básico: Mapear claves a valores

Imagina que queremos registrar los trofeos ganados por varios equipos deportivos:

type TeamName = 'Lakers' | 'Warriors' | 'Bulls' | 'Celtics';
 
// Construimos el tipo Record
type ChampionshipRecord = Record<TeamName, number>;

Ahora definimos nuestro objeto usando ChampionshipRecord:

const nbaChampionships: ChampionshipRecord = {
  Lakers: 17,
  Warriors: 7,
  Bulls: 6,
  Celtics: 18,
};
 
console.log(`Lakers trophies: ${nbaChampionships.Lakers}`);

Si escribes mal el nombre de un equipo o te olvidas de incluir alguno, TypeScript marcará el error en tiempo de compilación:

// Error: Property 'Celtics' is missing in type...
const incompleteRecords: ChampionshipRecord = {
  Lakers: 17,
  Warriors: 7,
  Bulls: 6,
};

Esta garantía es lo que hace que Record sea tan útil: obliga a que tu diccionario sea exhaustivo.


3. Patrón práctico: Manejo de estados de la aplicación y controladores

Un caso muy común en el mundo real es mapear códigos de estado de una orden a etiquetas visuales (badges) o controladores para la interfaz:

type OrderStatus = 'pending' | 'processing' | 'shipped' | 'delivered' | 'cancelled';
 
interface BadgeConfig {
  label: string;
  color: 'yellow' | 'blue' | 'purple' | 'green' | 'red';
}
 
const statusBadgeMap: Record<OrderStatus, BadgeConfig> = {
  pending: { label: 'Payment Pending', color: 'yellow' },
  processing: { label: 'In Preparation', color: 'blue' },
  shipped: { label: 'Out for Delivery', color: 'purple' },
  delivered: { label: 'Completed', color: 'green' },
  cancelled: { label: 'Order Cancelled', color: 'red' },
};

Si más adelante un product manager agrega un nuevo estado a OrderStatus (como 'refunded'), TypeScript marcará de inmediato statusBadgeMap con un error de compilación. Así nunca olvidarás actualizar un badge en tu interfaz.

¿Qué pasa si no quieres definir cada una de las claves desde el principio? Eso nos lleva a combinar Record con otros tipos utilitarios.


4. Combinar Record con Partial para diccionarios opcionales

A veces tienes un conjunto grande de claves posibles, pero tu objeto solo guarda una parte de ellas en un momento dado (como una caché de usuarios en memoria):

type UserId = string;
 
interface UserProfile {
  id: string;
  name: string;
  email: string;
}
 
// Partial permite que las claves sean opcionales
type UserCache = Partial<Record<UserId, UserProfile>>;
 
const activeUsers: UserCache = {
  'usr_101': { id: 'usr_101', name: 'Dany', email: 'dany@example.com' },
};

Usar Partial<Record<K, T>> te da la flexibilidad de un diccionario opcional y, al mismo tiempo, mantiene la verificación estricta de tipos en todos los valores guardados.


5. Patrón avanzado: Claves con Template Literals en Records

En TypeScript moderno, puedes combinar Record con template literal types para asegurar convenciones de nombres en tus claves:

type Environment = 'dev' | 'staging' | 'prod';
type ConfigKey = `API_URL_${Uppercase<Environment>}`;
 
// ConfigKey es "API_URL_DEV" | "API_URL_STAGING" | "API_URL_PROD"
const apiEndpoints: Record<ConfigKey, string> = {
  API_URL_DEV: 'https://dev.api.danywalls.com',
  API_URL_STAGING: 'https://staging.api.danywalls.com',
  API_URL_PROD: 'https://api.danywalls.com',
};

Este patrón garantiza que los miembros del equipo sigan convenciones de nombres exactas al declarar configuraciones de entorno o controladores de eventos.


6. Comparación: Record<K, T> vs Index Signatures vs Map<K, V>

Muchos desarrolladores me preguntan cuándo conviene usar un Record, un index signature ({ [key: string]: T }) o el objeto Map nativo de JavaScript.

Aquí tienes una comparación directa:

CaracterísticaRecord<K, T>Index Signature ({ [k: string]: T })Map<K, V> de JavaScript
Tipos de clave admitidosstring, number, symbol o unionesstring, number, symbol (sin uniones)Cualquier tipo (objetos, funciones, primitivos)
Comprobación exhaustiva de clavesSí (al usar una unión de claves)No (permite cualquier clave arbitraria)No
Serialización a JSONNativa (JSON.stringify)Nativa (JSON.stringify)Requiere conversión manual
Orden de iteraciónOrden de propiedades del objetoOrden de propiedades del objetoOrden de inserción garantizado
Rendimiento con eliminaciones frecuentesModeradoModeradoAlto (optimizado para mutaciones)

Cuándo elegir cada uno:

  • Usa Record<K, T>: cuando conoces las claves literales, tienes configuraciones estáticas o buscas una verificación exhaustiva en tiempo de compilación.
  • Usa Index Signature: cuando las claves son cadenas totalmente abiertas (por ejemplo, al procesar parámetros de consulta dinámicos).
  • Usa Map<K, V>: cuando necesitas claves que no sean strings, agregas y eliminas elementos con mucha frecuencia, o requieres lecturas rápidas con .size.

7. Cuidado: Búsquedas seguras con noUncheckedIndexedAccess

Por defecto, TypeScript asume que acceder a cualquier propiedad en un Record<string, T> siempre devolverá T:

const scores: Record<string, number> = {
  alice: 95,
};
 
// TypeScript asume que score es number, ¡pero en tiempo de ejecución es undefined!
const score = scores['bob'];
console.log(score.toFixed(2)); // TypeError en tiempo de ejecución: Cannot read properties of undefined

Para evitar este fallo en tiempo de ejecución, activa noUncheckedIndexedAccess en tu tsconfig.json:

{
  "compilerOptions": {
    "noUncheckedIndexedAccess": true
  }
}

Con esta opción habilitada, TypeScript infiere scores['bob'] como number | undefined. Esto te obliga a verificar si el valor existe antes de llamar a cualquier método sobre él.


Conclusión y siguientes pasos

El tipo utilitario Record aporta claridad y seguridad a los objetos de JavaScript:

  1. Asegura la completitud: Usa Record<MiUnion, TipoValor> para que TypeScript te obligue a manejar cada clave.
  2. Combina con otros utilitarios: Usa Partial<Record<K, T>> al crear cachés dinámicas o diccionarios de búsqueda.
  3. Activa verificaciones estrictas: Usa noUncheckedIndexedAccess en tu tsconfig.json para prevenir fallos en tiempo de ejecución por claves inexistentes.

Si quieres seguir mejorando tus habilidades con TypeScript, revisa estos artículos relacionados en mi blog:

Parte de la serie TypeScript

Mastering the language of the web. From basic types to advanced generics and patterns.

Ver serie completa

Preguntas Frecuentes

¿Qué es el tipo utilitario Record en TypeScript?

El tipo utilitario Record<K, T> construye un tipo de objeto cuyas claves de propiedad son del tipo K y cuyos valores de propiedad son del tipo T.

¿Cuál es la diferencia entre Record y un index signature?

Un index signature {[key: string]: T} permite cualquier string arbitrario como clave. Un Record te permite limitar las claves a uniones literales específicas (como 'admin' | 'guest'), garantizando una comprobación exhaustiva.

¿Cuándo debería elegir Map en lugar de Record en TypeScript?

Usa Record para diccionarios estáticos, objetos de configuración y datos JSON. Usa Map cuando necesites inserciones y eliminaciones frecuentes, claves que no sean strings o cuando necesites conservar el orden de inserción.

¿Por qué TypeScript permite acceder a claves undefined en un Record?

Por defecto, TypeScript asume que cada clave definida en Record<string, T> existe. Para forzar a TypeScript a incluir undefined en la búsqueda de propiedades, activa noUncheckedIndexedAccess en tu tsconfig.json.

Artículos Relacionados

Compartir este artículo

Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.


Software real. Lecciones reales.

Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.

User avatar
User avatar
User avatar
User avatar
+13K

Únete a 13,800+ desarrolladores y lectores.

Cero spam. Cancela tu suscripción en cualquier momento.