TypeScript 官方手冊翻譯計劃【十】:型別操控-對映型別

Chor發表於2021-12-09
  • 說明:目前網上沒有 TypeScript 最新官方文件的中文翻譯,所以有了這麼一個翻譯計劃。因為我也是 TypeScript 的初學者,所以無法保證翻譯百分之百準確,若有錯誤,歡迎評論區指出;
  • 翻譯內容:暫定翻譯內容為 TypeScript Handbook,後續有空會補充翻譯文件的其它部分;
  • 專案地址TypeScript-Doc-Zh,如果對你有幫助,可以點一個 star ~

本章節官方文件地址:Mapped Types

對映型別

有時候我們不想重複編寫程式碼,這時候就需要基於某個型別建立出另一個型別。

索引簽名用於為那些沒有提前宣告的屬性去宣告型別,而對映型別是基於索引簽名的語法構建的。

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

對映型別也是一種泛型型別,它使用 PropertyKey(屬性鍵)的聯合型別(通常通過 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 實現鍵的重新對映

在 TypeScript4.1 或者更高的版本中,你可以在對映型別中使用 as 子句實現鍵的重新對映:

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

你可以使用諸如模板字面量型別這樣的特性從原來的屬性名中去建立新的屬性名:

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

你可以通過條件型別產生 never 型別,從而過濾掉某些鍵:

// 移除 kind 屬性
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;
      }
      */         

不僅僅是 string | number | symbol 這樣的聯合型別,任意的聯合型別都可以對映:

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

對映型別的進一步應用

對映型別也可以和本章(型別操控)介紹的其它特性搭配使用。舉個例子,下面是一個使用了條件型別的對映型別,根據物件是否有一個設定為字面量 true 的屬性 pii,它會返回 true 或者 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;
            }
            */        

相關文章