TypeScript official manual translation plan [x]: type manipulation - mapping type

Keywords: Front-end TypeScript

  • Note: at present, there is no Chinese translation of the latest official documents of TypeScript on the Internet, so there is such a translation plan. Because I am also a beginner of TypeScript, I can't guarantee the 100% accuracy of translation. If there are errors, please point them out in the comment area;
  • Translation content: the tentative translation content is TypeScript Handbook , other parts of the translated documents will be supplemented when available;
  • Project address: TypeScript-Doc-Zh , if it helps you, you can click a star~

Official document address of this chapter: Mapped Types

mapping type

Sometimes we don't want to write code repeatedly, so we need to create another type based on one type.

Index signatures are used to declare types for properties that are not declared in advance, and mapping types are built based on the syntax of index signatures.

type OnlyBoolsAndHorses = {
    [key: string]: boolean | Horse;
};
const conforms: OnlyBoolsAndHorses = {    
    del: true,
    rodney: false,
};

The mapping type is also a generic type, which uses the union type of PropertyKey (usually through keyof Create) to traverse all keys to create a new type:

type OptionsFlags<Type> = {
    [Property in keyof Type]: boolean;
};

In this example, OptionsFlags will accept all attributes of Type type and change their values to Boolean values.

type FeatureFlags = {
    darkMode: () => void;
    newUserProfile: () => void;
};

type FeatureOptions = OptionsFlags<FeatureFlags>;
           ^
      /* 
      type FeatureOptions = {
            darkMode: boolean;
            newUserProfile: boolean;
      }     
      */ 

Mapping modifier

There are two additional modifiers for mapping, namely readonly and?, They are used to declare read-only and optional properties, respectively.

To remove or add a modifier, simply prefix the modifier with - or +. If no prefix is added, the default is +.

// Remove read-only property from type
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;
       }     
       */ 
// Removes the selectability of attributes in a type
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;
   }    
   */ 

Remapping keys through as

In typescript 4.1 or later, you can use the as clause in the mapping type to realize key remapping:

type MappedTypeWithNewProperties<Type> = {
    [Properties in keyof Type as NewKeyType]: Type[Properties]
}

You can use things like Template literal type This feature creates a new attribute name from the original attribute name:

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

You can filter out some keys by generating a never type from a condition type:

// Remove kind attribute
type Exclude<T,U> = T extends U ? never : T
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;
      }
      */         

Not only the union type such as string | number | symbol, any union type can be mapped:

type EventConfig<Events extends { kind: string }> = {
    [E in Events as E['kind']]: (event: E) => void;
}

type SqureEvent = { kind: 'squre', x: number, y: number };
type CircleEvent = { kind: 'circle', radius: number };

type Config = EventConfig<SqureEvent | CricleEvent>
       /**
       type Config = {
            squre: (event: SqureEvent) => void;
            circle: (event: CircleEvent) => void;
       }
       /    

Further application of mapping type

Mapping types can also be used with other features described in this chapter (type manipulation). For example, here is an example Mapping type using condition type , depending on whether the object has a property pii set to literal true, it will return true or false:

type ExtractPII<Type> = {
    [Property in keyof Type]: Type[Property] extends { pii: true } ? true : false
};

type DBFileds = {
    id: { format: 'incrementing' };
    name: { type: string; pii: true };
};

type ObjectsNeedingGDPRDeletion = ExtractPII<DBFileds>;
                ^
            /*
            type ObjectsNeedingGDPRDeletion = {
                id: false;
                name: true;
            }
            */        

Posted by kanch33 on Fri, 03 Dec 2021 00:58:49 -0800