Los tipos mapeados en TypeScript son potentes herramientas para transformar las propiedades de un tipo en otro. Son similares a los métodos de array como map y filter, pero estas operaciones se realizan sobre tipos.
Entenderemos su uso a través de ejemplos prácticos. A continuación, mostraremos progresivamente 8 ejemplos de tipos mapeados, desde los más básicos hasta los más avanzados, que te permitirán dominar fácilmente esta potente herramienta de transformación de tipos.
Transformación básica de tipos
En TypeScript, a veces necesitamos transformar las propiedades de un tipo en otro. Esto puede lograrse fácilmente utilizando tipos mapeados.
A continuación, demostraremos cómo transformar las propiedades de un tipo Product en tipos string
a través de un ejemplo concreto.
Definir el tipo Producto
En primer lugar, definimos un tipo Producto, que incluye tres propiedades: nombre (tipo cadena), precio (tipo número), e inStock (tipo booleano).
type Product = {
name: string;
price: number;
inStock: boolean;
};
Definir el tipo ProductToString
A continuación, definimos un nuevo tipo ProductToString
, que transforma todas las propiedades del tipo Product en tipos string.
type ProductToString = {
[Key in keyof Product]: string;
};
Tipo resultante
Finalmente, el tipo ProductToString
resultante es el siguiente:
type ProductToString = {
name: string;
price: string;
inStock: string;
};
Hacer opcionales las propiedades de un tipo
En TypeScript, a menudo necesitamos hacer que todas las propiedades de un tipo sean opcionales. Normalmente, usamos el tipo de utilidad Partial incorporado para conseguirlo, pero también podemos conseguir el mismo efecto usando tipos mapeados.
Definir tipo de producto
type Product = {
name: string;
price: number;
inStock: boolean;
};
Utilizar tipos asignados para que las propiedades sean opcionales
type ProductToOptional = {
[Key in keyof Product]?: Product[Key];
};
Tipo resultante
type ProductToOptional = {
name?: string;
price?: number;
inStock?: boolean;
};
Convertir propiedades opcionales en obligatorias
En TypeScript, a veces necesitamos convertir todas las propiedades opcionales de un tipo en propiedades requeridas. Esto se puede lograr fácilmente utilizando tipos mapeados.
Definir tipo de producto
type Product = {
name?: string;
price?: number;
inStock?: boolean;
};
Definir el tipo ProductToRequired
type ProductToRequired = {
[Key in keyof Product]-?: Product[Key];
};
Tipo resultante
type ProductToRequired = {
name: string;
price: number;
inStock: boolean;
};
Hacer que las propiedades sean de sólo lectura
En TypeScript, a veces necesitamos que todas las propiedades de un tipo sean de sólo lectura. Esto se puede lograr fácilmente utilizando tipos mapeados.
Definir tipo de producto
type Product = {
name: string;
price: number;
inStock: boolean;
};
Definir el tipo ProductToReadonly
type ProductToReadonly = {
readonly [Key in keyof Product]: Product[Key];
};
Tipo resultante
type ProductToReadonly = {
readonly name: string;
readonly price: number;
readonly inStock: boolean;
};
Eliminación de ciertas propiedades
En TypeScript, a veces necesitamos eliminar ciertas propiedades de un tipo. Por lo general, utilizamos el tipo de utilidad Omit incorporado para lograr esto, pero también podemos lograr el mismo efecto utilizando tipos mapeados.
Definir tipo de producto
type Product = {
name: string;
price: number;
inStock: boolean;
};
Utilizar tipos asignados para eliminar propiedades
type ProductWithoutPrice = {
[Key in keyof Product as Key extends 'price' ? never : Key]: Product[Key];
};
Tipo resultante
type ProductWithoutPrice = {
name: string;
inStock: boolean;
};
Creación de un tipo sólo con propiedades de un tipo específico
En TypeScript, podemos utilizar tipos condicionales para crear un nuevo tipo que sólo incluya propiedades de un determinado tipo.
Definir tipo de producto
type Product = {
name: string;
price: number;
inStock: boolean;
tags: string[];
};
Definir el tipo OnlyStringProperties
type OnlyStringProperties<Type> = {
[Key in keyof Type as Type[Key] extends string ? Key : never]: Type[Key];
};
Utilizar OnlyStringProperties
type ProductOnlyStringProperties = OnlyStringProperties<Product>;
Tipo resultante
type ProductOnlyStringProperties = {
name: string;
};
Creación de nuevos nombres de propiedades usando tipos literales de plantilla
En TypeScript, podemos utilizar tipos literales de plantilla para crear un nuevo tipo con nombres de propiedades que tengan un prefijo específico y mayúsculas.
Definir Tipo Produce
type Product = {
name: string;
price: number;
inStock: boolean;
};
Creación de un tipo con propiedades prefijadas por get
En TypeScript, podemos usar tipos literales de plantilla para crear un nuevo tipo con nombres de propiedades prefijados por get.
type Getters<Type> = {
[Key in keyof Type as `get${Capitalize<string & Key>}`]: () => Type[Key];
};
Utilizar captadores
type ProductGetters = Getters<Product>;
Tipo resultante
type ProductGetters = {
getName: () => string;
getPrice: () => number;
getInStock: () => boolean;
};
Tipos mapeados anidados basados en condiciones
En TypeScript, podemos crear una lógica de transformación de tipos más compleja combinando tipos mapeados y tipos condicionales. Por ejemplo, podemos generar diferentes estructuras de tipos anidados basadas en los tipos de propiedades.
Definir tipo NestedObject
Primero, definimos un tipo NestedObject
, que incluye varios tipos de propiedades, incluyendo objetos anidados.
type NestedObject = {
id: number;
name: string;
metadata: {
createdAt: Date;
updatedAt: Date;
};
tags: string[];
};
Definir el tipo DeepReadonly
A continuación, definimos un tipo DeepReadonly
que convertirá todas las propiedades de un objeto, incluidas las propiedades de los objetos anidados, en sólo lectura.
type DeepReadonly<T> = {
readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};
En esta definición, T[P] extends
object se utiliza para comprobar si el tipo de propiedad es un objeto. Si es un objeto, DeepReadonly
se aplica recursivamente; en caso contrario, la propiedad se establece como de sólo lectura.
Utilizar el tipo DeepReadonly
Podemos utilizar DeepReadonly
para definir un nuevo tipo ReadonlyNestedObject
, que es una versión de sólo lectura profunda de NestedObject
.
type ReadonlyNestedObject = DeepReadonly<NestedObject>;
Tipo resultante
Finalmente, el tipo ReadonlyNestedObject
resultante es el siguiente:
type ReadonlyNestedObject = {
readonly id: number;
readonly name: string;
readonly metadata: {
readonly createdAt: Date;
readonly updatedAt: Date;
};
readonly tags: readonly string[];
};
Ejemplo de uso
const readonlyNestedObject: ReadonlyNestedObject = {
id: 1,
name: "Example",
metadata: {
createdAt: new Date(),
updatedAt: new Date()
},
tags: ["typescript", "programming"]
};
// readonlyNestedObject.id = 2; // Error: Cannot assign to 'id' because it is a read-only property.
// readonlyNestedObject.metadata.createdAt = new Date(); // Error: Cannot assign to 'createdAt' because it is a read-only property.
Los tipos mapeados en TypeScript son una característica increíblemente poderosa que nos permite lograr varias transformaciones de tipo complejas. Se pueden utilizar para:
- Transformar propiedades: Cambiar los tipos de las propiedades existentes en un tipo.
- Añadir o eliminar propiedades: Añadir nuevas propiedades o eliminar las existentes.
- Controlar la opcionalidad y el estado de sólo lectura: Haga que las propiedades sean opcionales o de sólo lectura.
- Crear tipos dinámicos: Construya nuevos tipos utilizando tipos condicionales y tipos literales de plantilla, adecuados para escenarios avanzados (por ejemplo, generando getters y setters).
- Mientras que los tipos de utilidad incorporados como
Partial
,Readonly
yOmit
proporcionan atajos convenientes, los tipos mapeados nos dan una comprensión más profunda y un control preciso sobre los tipos.
Espero que este artículo te ayude a dominar mejor estas técnicas, haciendo tu código más limpio, más predecible y más fácil de mantener.