iT邦幫忙

2021 iThome 鐵人賽

DAY 15
1
Modern Web

React 前端工程師的兩把刷子系列 第 15

[Day15] TS:在 Mapped Type 中使用 Template Literal 來改物件型別中的所有 key

Mapped Types

上面這個是今天會提到的內容,如果你已經可以輕鬆看懂,歡迎直接左轉去看我隊友們的精彩文章!

昨天我們已經學到了 Mapped Types 的精華,就是可以在 Index Signatures 中使用 in 來疊代 in 後面的所有元素,便可以透過 Mapped Type 修改物件型別中所有屬性值的型別。因此,可以把原本的 SupportedEvent 進行修改:

type SupportedEvent = {
  click: string;
  change: string;
  keyup: string;
  keydown: string;
};

透過 HandledEvent 這個 Utility Type 進行轉換後,產生出新的型別:

type HandledEvent = {
  [K in keyof SupportedEvent]: () => void;
};

更好的方式還可以把 Supported Event 變成一個泛型的參數,讓它變成 Utility Type,像是這樣,如此 MappedValuesToFunction 就可以變的更泛用:

type MappedValuesToFunction<T> = {
  [K in keyof T]: () => void;
};
type HandledEvent = MappedValueToFunction<SupportedEvent>;

修改物件型別的所有 key

從昨天的內容中,我們已經知道如何透過 Mapped Types 「一次修改所有 SupportedEvent 屬性值的型別」,最後得到的 HandledEvent 會是:

type HandledEvent = {
  click: () => void;
  change: () => void;
  keyup: () => void;
  keydown: () => void;
};

如果現在我們不是要修改物件型別中屬性值的型別,而是想要修改屬性 key 的名稱時,可以怎麼做呢?舉例來說,我們想要根據 HandledEvent ,透過某個 Utility Type 後可以產生出像這樣的型別:

type EventHandler = {
  handleClick: () => void;
  handleChange: () => void;
  handleKeyup: () => void;
  handleKeydown: () => void;
};

這時候就可以用到今天要提的這個 Utility Type:

Mapped Types

讓我們來依序拆解並理解這個寫法。

Mapped Type + keyof Type Operator

Mapped Types

首先,從 in 這個關鍵字可以看到這裡用了 Mapped Type,而 [K in keyof T] 的意思就是把 T 這個物件型別的所有 key 取出組成 union types,以這裡來說,如果寫 ToEventHandler<HandledEvent> 的話,這裡的 keyof T 應該就會變成 "click" | "change" | "keyup" | "keydown"

keyof

所以說 [K in keyof T] 就會是用疊代的方式,每次取出 "click" | "change" | "keyup" | "keydown" 中的元素來跑迴圈,可以想像組出來的東西應該會像這樣:

type HandledEvent = {
  click: '...';
  change: '...';
  keyup: '...';
  keydown: '...';
};

搭配 as 使用 Template Literal

接著我們看到這裡有 Template Literal 的用法,也就是 `${}` 的用法,並且搭配關鍵字 as 來使用:

Mapped Type

透過 as 後面接字串型別的方式,就可以讓我們達到修改屬性 key 的目的。

現在為了方便理解,我們先把 Template Literal 中的 Capitalize 拿掉:

type ToEventHandler<T> = {
  [K in keyof T as `handle${string & K}`]: T[K];
};

這時候應該會看到經過這個 Utility Type 後跑出來的型別會是:

type EventHandler = ToEventHandler<HandledEvent>;

// 預期 EventHandler 會長這樣
type EventHandler = {
  handleclick: '...';
  handlechange: '...';
  handlekeyup: '...';
  handlekeydown: '...';
};

這裡我們已經成功透過 Mapped Type 搭配 as 和 Literal Template 的使用,成功把物件型別的 key 進行了轉換,讓每個 key 的最前面都多了 handle... 的前綴。

但因為在 JavaScript 中,通常函式的命名會用小寫駝峰的方式,例如,handleClickhandleChange,因此這裡可以在搭配使用在 Day12 時曾經提到的 Intrinsic String Manipulation Types ,透過 Capitalize 來將字串型別的第一個字轉成英文大寫,因此如果改會原本的寫法 as `handle${Capitalize<string & K>}` 後,產生出來的 key 的名稱就會是我們想要的以 handle 作為開頭的 key:

type EventHandler = ToEventHandler<HandledEvent>;

// 預期 EventHandler 會長這樣
type EventHandler = {
  handleClick: '...';
  handleChange: '...';
  handleKeyup: '...';
  handleKeydown: '...';
};

Mapped Type 中的 K (key) 是可以拿來使用的

來看最後一個部分:

Mapped Types

這裡我們把 Mapped Types 跑迴圈是的變數取名為 K,而不論取名是 KP,它都只是個變數名稱,要取名成什麼都可以,但實際上它也不單單只是個名稱,它還可以被拿來被後續使用。

這裡 [...]: T[K]: 後面放的就是這個屬性值的型別,我們知道 T 就是我們帶進去的物件型別,而 K 其實就是沒有次跑迴圈時的 key,因此 T[K] 就是我們在 Day05 曾提過的 Indexed Access Types,意思就是直接把該屬性值原本的型別取出來就好。

總結

現在總結來看,應該就可以知道怎麽透過 Mapped Type 搭配 as 和 Template Literal 來修改物件型別中的屬性名稱:

type SupportedEvent = {
  click: string;
  change: string;
  keyup: string;
  keydown: string;
};

type MappedValuesToFunction<T> = {
  [K in keyof T]: () => void;
};
type HandledEvent = MappedValuesToFunction<SupportedEvent>;

type ToEventHandler<T> = {
  [K in keyof T as `handle${Capitalize<string & K>}`]: T[K];
};
type EventHandler = ToEventHandler<HandledEvent>;

這裡最終的 EventHandler 就會是:

type EventHandler = {
  handleClick: () => void;
  handleChange: () => void;
  handleKeyup: () => void;
  handleKeydown: () => void;
};

範例程式碼

https://tsplay.dev/mAVZRW @ TypeScript Playground

參考資料


上一篇
[Day14] TS:什麼!TypeScript 中還有迴圈的概念 - 用 Mapped Type 操作物件型別
下一篇
[Day16] TS:在 Mapped Type 中修改物件的 property modifiers:理解 Partial、Required 和 Readonly 的實作
系列文
React 前端工程師的兩把刷子30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0

果然大神的文章都是最後一刻才會出現,今天又學會了一把刷子了

pjchender iT邦新手 3 級 ‧ 2021-10-01 13:59:30 檢舉

其實真的很謝謝你們的支持!/images/emoticon/emoticon08.gif

我要留言

立即登入留言