沒想到已經第29天了,這個系列雖然蠻隨意的,但還是有稍微安排主題順序,而今天的mapped types是我之前大略瀏覽官方文件 - Creating Types from Types 一章 後,覺得最實用的取得型別技巧,所以特地放在第29天。
不過我發現之前講interface忘記提到型別簽署(type signatures),所以今天就和mapped types一併認識吧。
根據以下引用的兩個官方文件定義,可以大略知道,mapped types是「基於物件型別簽署(type signatures)語法,透過取得物件屬性鍵值所創造出來的一種泛型(generic type)」。
- Mapped types build on the syntax for index signatures.
- A mapped type is a generic type which uses a union of
PropertyKeys
to iterate through keys to create a type.
光看定義會有點模糊,以下就一步一步來拆解何謂mapped types。
所謂的「型別簽署(type signatures)」是指定義物件型別時,在無法事先知道物件的屬性(或稱鍵值,key)名稱的情況下,可以只用屬性名稱的型別和屬性值的型別來定義物件型別,也就是不給定屬性名稱的意思,來看型別簽署的範例:
interface phoneDictionary {
[key: string]: number| null;
}
const phoneDirectory: phoneDictionary = {
Alison: 88652222677,
Bob: 88675433335,
Charis: null,
David: 88688000000,
// ...
}
如上面範例,我們想創造一個屬性名稱為人名,但屬性值只能是number或null型別的電話簿型別(phoneDictionary),但電話簿無法事先得知會有那些人名當作屬性,也不適合事先加入。
因此可以用型別簽署的方式只定義屬性名稱的型別 ─ 意即無論是什麼屬性名稱,只要屬性(鍵值)型別是string型別就符合這個型別的規範。
現在回來mapped types。
Mapped types是利用型別簽署可以不用事先定義屬性名稱的性質,在不知道屬性名稱有哪些的情況下,取得某個物件的屬性名稱來創造另一個型別。
有點像是複製某個物件型別的屬性名稱後,來建立另一種有相同屬性名稱、但可能有不同型別屬性值的物件型別。
至於複製屬性名稱這件事,可以利用先前學過的generics、in
關鍵字和 keyof
型別運算子來達成:
type MappedKeys<T> = {
[Key in keyof T]: any;
}
keyof
型別運算子是用來取得物件屬性名稱(或說 屬性鍵值 )並union成一個型別;而 in
關鍵字顧名思義就是限制新建立的物件型別的屬性鍵值必須是這個union裡的其中一個。
直接舉例來看,假設我想要擴充這個電話簿,希望電話簿的每個人都有姓名、電話和住址資訊,因此想利用原來電話簿儲存的人名創造另一個物件型別:
type CopyDictionary<T, V> = {
[Key in keyof T]: V;
}
type Classmate = {name: string; phone: number | null; address: string | null};
type ClassmateDictionary = CopyDictionary<typeof phoneDirectory, Classmate>;
const classmates: ClassmateDictionary = {
Alison: {
name: "Alison",
phone: 88652222677,
address: ""
},
Bob: {
name: "Bob",
phone: 88675433335,
address: ""
},
David:{
name: "David",
phone: 88688000000,
address: ""
},
// ...
}
keyof
型別運算子在這個範例裡會是 keyof typeof phoneDirectory
,也就會得到 "Alison" | "Bob" | "David" | ...
,因此就能用這個union輕鬆地建立另一個有相同屬性名稱的物件。
有兩個跟mapped types比較相關的修飾子需要特別說一下,分別是: +
、-
。
+
就如同字面上的意思是「要加入這個屬性鍵值」,而mapped types預設會「加入」取得的屬性鍵值。
-
則是移除屬性鍵值,例如可以這麼使用:
type CopyDictionary<T, V> = {
- readonly [Key in keyof T]: V;
}
因此屬性鍵值不再會是readonly(只能讀取),而是可以修改的。
type CopyDictionary<T, V> = {
[Key in keyof T] -?: V;
}
意即所有屬性都不是optional property。
官方文件還有很多是跟前面介紹過的關鍵字和運算子,例如 type assertion( as
)、conditional types和template literal types等一併使用的範例。
由於這篇是聚焦在mapped types本身的語法,以及和mapped types比較相關的+
、-
,因此就點到這裡為止;若想知道如何將mapped types與其他關鍵字和運算子使用的人,可以參酌官方文件的說明。
最後附上文中mapped types的範例結束這回合~
參考資料
Mapped Types @TypeScript Handbook
Index Signatures @TypeScript Handbook
Keyof Type Operator @TypeScript Handbook