• 首页
  • vue
  • TypeScript
  • JavaScript
  • scss
  • css3
  • html5
  • php
  • MySQL
  • redis
  • jQuery
  • 映射类型

    有的时候,一个类型需要基于另外一个类型,但是你又不想拷贝一份,这个时候可以考虑使用映射类型。

    映射类型基于索引签名的语法,用于声明尚未提前声明的属性类型:

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

    映射类型是一种泛型类型,它使用PropertyKeys的联合(通常通过keyof创建)来遍历键以创建类型:

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

    本例子中,OptionsFlags会遍历Type所有的属性,然后设置为布尔类型。

    type FeatureFlags = {
      darkMode: () => void;
      newUserProfile: () => void;
    };
    
    type FeatureOptions = OptionsFlags<FeatureFlags>;
    // type FeatureOptions = {
    //    darkMode: boolean;
    //    newUserProfile: boolean;
    // }
    


    映射修饰符

    在使用映射类型时,会涉及到两个修饰符:一个是readonly,用于设置属性只读;一个是?,用于设置属性可选。

    你可以通过前缀-或者+删除或者添加这些修饰符。如果没有写前缀,相当于使用了前缀+

    // 删除属性中的只读属性
    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;
    }*/
    
    // 删除属性中的可选属性
    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;
    }*/
    


    通过 as 实现键名重新映射

    在 TypeScript 4.1 及以后,你可以在映射类型中使用as语句实现键名重新映射:

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

    可以利用模板字面量类型(template literal types),从以前的属性名称创建新的属性名称:

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


    TypeScript 内置工具Exclude<T,U>,把 T 当中属于 U 的部分去除。


    你也可以利用条件类型返回一个never从而过滤掉键:

    // Remove the 'kind' property
    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;
    }*/
    


    你还可以遍历任何联合类型,不仅仅是string | number | symbol这种联合类型,可以是任何类型的联合:

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


    深入探索

    映射类型也可以跟其他的功能搭配使用,举个例子,这是一个使用条件类型的映射类型,会根据对象是否有pii属性返回true或者false:

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