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:

Prueba este código ↗

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 PropertyKeys (creadas con frecuencia mediante un keyof) para iterar a través de claves para crear un tipo:

Prueba este código ↗

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.

Prueba este código ↗

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 +.

Prueba este código ↗

// 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;
}

Prueba este código ↗

// 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:

Prueba este código ↗

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:

Prueba este código ↗

// 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:

Prueba este código ↗

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:

Prueba este código ↗

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;
}
Última actualización