[Day18] TS:理解 Omit 的實作
是我們今天要聊的內容,老樣的,如果你已經可以輕鬆看懂,歡迎直接左轉去看同事 Ken 精彩的文章 — 「From State Machine to XState」。
昨天我們說明到在 TypeScript 中內建的 Utility Type Pick
可以幫助開發者從物件型別中挑出想要的屬性,以次建立出新的型別:
type Person = {
firstName: string;
lastName: string;
age: number;
};
type PersonName = Pick<Person, 'firstName' | 'lastName'>;
今天我們繼續延續這個例子的使用,但是要把它的功能反過來,也就是說 Pick
是挑出想要的元素,現在則來寫一個 Omit
,讓它是從物件型別中剔除不想要的屬性。
先從 Mapped Types 的角度思考可以怎麼寫:
type Person = {
firstName: string;
lastName: string;
age: number;
};
type PersonName = {
[P in keyof P]: Person[P];
};
一開始 Mapped Types 可以寫成這樣,但這樣就是最單純的複製一個物件型別的意思。
接著從所有的屬性 Key 中剔除 age
的屬性,變成:
type PersonName = {
[P in Exclude<keyof Person, 'age'>]: Person[P];
};
這裡使用 Day09 是提過的 Exclude
,而 Exclude<keyof Person, 'age'>
在這裡就等同於 'firstName' | 'lastName'
。寫到這裡,出來的型別其實就已經符合想要的結果:
在完成了預期的功能後,接著來看看能不能讓它變成一個更泛用的 Utility Type。首先,把帶入的物件型別 Person
變成一個可以帶入的泛型 T
:
// 把原本直接使用的 `Person` 變成參數 `T`
type ToPersonName<T> = {
[P in Exclude<keyof T, 'age'>]: T[P];
};
再來把希望被剔除的參數也變成一個泛型參數 K
:
// 把原本直接使用的 'age' 變成參數 `K`
type ToPersonName<T, K> = {
[P in Exclude<keyof T, K>]: T[P];
};
看起來已經相當泛用了,現在已經可以透過這個 Utility Type 從物件型別 T
中剔除掉不想要的屬性 Keys K
,到這裡雖然可以結束了,但讓我們回過頭來看一下昨天寫的 Pick<Type, Keys>
的原始碼:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
你有沒有發現 ToPersonName
的整個輪廓其實和 Pick
非常接近:
也就是說,這裡的 ToPersonName
其實只要用 Pick
就可以完成了,把原本的 ToPersonName
改用 Pick
來寫:
// 把原本的 `ToPersonName` 改用 `Pick` 來寫
type ToPersonName<T, K> = Pick<T, Exclude<keyof T, K>>;
江江~發現了嗎,你已經寫出和 TypeScript 官方提供的 Omit
差不多的原始碼了。在原本的 Omit
中,為了確保 K
一定是可以被放入 Mapped Types 中使用的,所以有對 K
多加上了 extends keyof any
的泛型限制:
// Construct a type with the properties of T except for those in type K
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
從這兩天的內容中,讀者都可以發現,可以先根據實際的情況撰寫出想要的結果後,再把可以抽成參數的部分變成泛型,最後變成一個更泛用的 Utility Types。
範例程式碼
https://tsplay.dev/W4pqaW @ TypeScript Playground
參考資料
Omit @ TypeScript > References > Utility Types