Tipos mapeados
Cuando no quieres repetirte, a veces un tipo necesita basarse en otro tipo.
Los tipos mapeados se basan en la sintaxis de las firmas de índice, que se utilizan para declarar los tipos de propiedades que no se han declarado con anticipación:
type OnlyBoolsAndHorses = {
[key: string]: boolean | Horse;
};
const conforms: OnlyBoolsAndHorses = {
del: true,
rodney: false,
};
Un tipo mapeado es un tipo genérico que usa una unión de PropertyKey
s (creadas con frecuencia mediante un keyof
) para iterar a través de claves para crear un tipo:
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
En este ejemplo, OptionsFlags
tomará todas las propiedades del tipo Type
y cambiará sus valores para que sean booleanos.
type Features = {
darkMode: () => void;
newUserProfile: () => void;
};
type FeatureOptions = OptionsFlags<Features>;
type FeatureOptions = {
darkMode: boolean;
newUserProfile: boolean;
}
Modificadores de mapeo
Hay dos modificadores adicionales que se pueden aplicar durante el mapeo: readonly
y ?
que afectan la mutabilidad y la opcionalidad respectivamente.
Puedes eliminar o agregar estos modificadores anteponiendo -
o +
. Si no agregas un prefijo, se supone +
.
// Elimina los atributos de 'readonly' de las propiedades de un tipo
type CreateMutable<Type> = {
-readonly [Property in keyof Type]: Type[Property];
};
type LockedAccount = {
readonly id: string;
readonly name: string;
};
type UnlockedAccount = CreateMutable<LockedAccount>;
type UnlockedAccount = {
id: string;
name: string;
}
// Elimina atributos 'optional' de las propiedades de un tipo
type Concrete<Type> = {
[Property in keyof Type]-?: Type[Property];
};
type MaybeUser = {
id: string;
name?: string;
age?: number;
};
type User = Concrete<MaybeUser>;
type User = {
id: string;
name: string;
age: number;
}
Remapeo de claves con as
En TypeScript 4.1 y posteriores, puedes remapear claves en tipos mapeados con una cláusula as
en un tipo mapeado:
type MappedTypeWithNewProperties<Type> = {
[Properties in keyof Type as NewKeyType]: Type[Properties]
}
Puedes aprovechar funciones como tipos literales de plantilla para crear nuevos nombres de propiedades a partir de las anteriores:
type Getters<Type> = {
[Property in keyof Type as `get${Capitalize<string & Property>}`]: () => Type[Property]
};
interface Person {
name: string;
age: number;
location: string;
}
type LazyPerson = Getters<Person>;
type LazyPerson = {
getName: () => string;
getAge: () => number;
getLocation: () => string;
}
Puedes filtrar claves generando never
mediante un tipo condicional:
// Elimina la propiedad 'kind'
type RemoveKindField<Type> = {
[Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
};
interface Circle {
kind: "circle";
radius: number;
}
type KindlessCircle = RemoveKindField<Circle>;
type KindlessCircle = {
radius: number;
}
Puedes mapear uniones arbitrarias, no solo uniones de string | number | symbol
, sino uniones de cualquier tipo:
type EventConfig<Events extends { kind: string }> = {
[E in Events as E["kind"]]: (event: E) => void;
}
type SquareEvent = { kind: "square", x: number, y: number };
type CircleEvent = { kind: "circle", radius: number };
type Config = EventConfig<SquareEvent | CircleEvent>
type Config = {
square: (event: SquareEvent) => void;
circle: (event: CircleEvent) => void;
}
Exploración adicional
Los tipos mapeados funcionan bien con otras características en esta sección de manipulación de tipos, por ejemplo aquí está un tipo mapeado usando un tipo condicional que devuelve true
o false
dependiendo de si un objeto tiene la propiedad pii
establecida en el literal true
:
type ExtractPII<Type> = {
[Property in keyof Type]: Type[Property] extends { pii: true } ? true : false;
};
type DBFields = {
id: { format: "incrementing" };
name: { type: string; pii: true };
};
type ObjectsNeedingGDPRDeletion = ExtractPII<DBFields>;
type ObjectsNeedingGDPRDeletion = {
id: false;
name: true;
}