iT邦幫忙

2022 iThome 鐵人賽

DAY 30
0
Modern Web

Hello TypeScript 菜鳥系列 第 30

Day 29. TypeScript Type Manipulation: Mapped Types

  • 分享至 

  • xImage
  •  

沒想到已經第29天了,這個系列雖然蠻隨意的,但還是有稍微安排主題順序,而今天的mapped types是我之前大略瀏覽官方文件 - Creating Types from Types 一章 後,覺得最實用的取得型別技巧,所以特地放在第29天。

不過我發現之前講interface忘記提到型別簽署(type signatures),所以今天就和mapped types一併認識吧。


根據以下引用的兩個官方文件定義,可以大略知道,mapped types是「基於物件型別簽署(type signatures)語法,透過取得物件屬性鍵值所創造出來的一種泛型(generic type)」。

  1. Mapped types build on the syntax for index signatures.
  1. 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)

所謂的「型別簽署(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。

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輕鬆地建立另一個有相同屬性名稱的物件。


Prefix: + -

有兩個跟mapped types比較相關的修飾子需要特別說一下,分別是: +-

+ 就如同字面上的意思是「要加入這個屬性鍵值」,而mapped types預設會「加入」取得的屬性鍵值。

- 則是移除屬性鍵值,例如可以這麼使用:

  1. 移除屬性鍵值的readonly修飾子
type CopyDictionary<T, V> = {
	- readonly [Key in keyof T]: V;
}

因此屬性鍵值不再會是readonly(只能讀取),而是可以修改的。

  1. 移除屬性鍵值的undefined性質
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


上一篇
Day 28. TypeScript Type Manipulation:Template Literal Types
下一篇
Day 30. 完賽結語
系列文
Hello TypeScript 菜鳥31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言