跳至主要内容

[Day18] TS:理解 Omit 的實作

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'。寫到這裡,出來的型別其實就已經符合想要的結果:

Omit

在完成了預期的功能後,接著來看看能不能讓它變成一個更泛用的 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 非常接近:

Pick and Omit

也就是說,這裡的 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