跳至主要内容

[Day16] TS:在 Mapped Type 中修改物件的 property modifiers:理解 Partial、Required 和 Readonly 的實作

Required

這是我們今天要聊的內容,老樣的,如果你已經可以輕鬆看懂,歡迎直接左轉去看同事 Kyle 的精彩文章 — 「今晚,我想來點 Web 前端效能優化大補帖!」。

Day14 我們理解了如何透過 Mapped Type 修改物件型別中屬性的 value,在 Day15 中我們則理解了如何透過 Mapped Type 來修改物件型別中屬性的 key,看起來好像已經能完整操作物件型別了,但等等,在 TypeScript 的物件型別中還有一個是 Property Modifiers(屬性修飾符)。

Property Modifiers

屬性修飾符(Property Modifiers)這個詞你可能沒聽過,但應該一定用過,像是透過在屬性名稱後面加上 ? 讓該屬性變成是 optional 的,或是透過 readonly 讓該屬性在 type-checking 時不能被修改,另外像是 Day14 提過的 Index Signatures 也算是 Property Modifiers 的一種:

// Property Modifiers: ?, readonly
interface Person {
firstName: string;
lastName?: string; // lastName is optional
readonly age: number; // you should not mutate the age
}

Mapped Modifiers

在知道 Property Modifiers 後,讓我們來看看如何透過 Mapped Types 來修改屬性的 Property Modifiers。

如果要在 Mapped Types 中添加或移除 Property Modifiers 的話(像是把所有的屬性都變成 optional),需要用到 +-,預設沒寫的話就是 + ,來看下面幾個例子。

Optional Properties

要改變物件屬性是否為 optional,只需要使用 +?-? 即可。若要把每個物件型別的屬型都變 optional,可以使用 +?? 都可以,因為預設就會是 +

Mapped Modifiers

相反的如果要讓物件型別全部的屬性都不是 optional 的話,可以用 -?,但這裡的減號就不能省略:

Mapped Modifiers

實際上使用這兩個 Type Utility 的話,效果會像是這樣:

interface Person {
firstName: string;
lastName?: string; // lastName is optional
readonly age: number; // you should not mutate the age
}

// 把每個物件型別的屬性都變成 optional
type ToOptionalProperty<T> = {
[K in keyof T]+?: T[K];
};

// 把每個物件型別的屬性的 optional 都移除
type RemoveOptionalProperty<T> = {
[K in keyof T]-?: T[K];
};

type PersonWithOptionalProps = ToOptionalProperty<Person>;
type PersonWithoutOptionalProps = RemoveOptionalProperty<Person>;

一開始 Person 中並不是每個 Property 都是 optional 的,但經過 ToOptionalProperty,所有 Properties 就都會是 Optional 的:

Mapped Modifiers

RemoveOptionalProperty 則能夠以此類推,使用 -? 後所有物件屬性原本的 optional 也都會被移除。

Readonly Properties

使用 Mapped Modifiers 添加或移除 readonly 屬性的方式也完全一樣,也是透過 +-

// 把每個物件型別的屬性都加上 readonly
type ToReadOnlyProperty<T> = {
+readonly [K in keyof T]: T[K]; // 最前面的 + 可以省略
};

// 把每個物件型別的屬性都移除 readonly
type RemoveReadOnlyProperty<T> = {
-readonly [K in keyof T]: T[K];
};

type PersonWithReadonlyProps = ToReadonlyProperty<Person>;
type PersonWithoutReadonlyProps = RemoveReadonlyProperty<Person>;

使用起來的效果就和 optional 時的說明一樣,如果針對 Person 使用了 RemoveReadonlyProperty 的話,原本的 readonly modifier 會被移除:

Mapped Modifiers

根據 Mapped Modifiers 實作出來的 Utility Types

實際上根據筆者個人經驗,比較少直接用的 Mapped Modifiers 來操作,不是它們不常被用到,而是因為 TypeScript 已經把它們包成幾個常用的 Utility Types,像是 Partial<Type>Required<Type>Readonly<Type> ,這三個 Type Utilities 都是在 Mapped Types 中使用 Mapped Modifiers 來做的操作,未來會再看到更多 Mapped Types 的延伸變化,就能理解 Mapped Type 有多強大。

有了 Mapped Modifiers 的概念後,這幾個 Utility Types 的原始碼都會很好理解。其中最常用的 Partial<Type> 其原始碼是:

// Make all properties in T optional
type Partial<T> = {
[P in keyof T]?: T[P];
};

跟我們剛剛寫的 ToOptionalProperty 是不是一模一樣呢?

然後是 Required<Type> 的原始碼:

// Make all properties in T required
type Required<T> = {
[P in keyof T]-?: T[P];
};

是不是也和剛剛寫的 RemoveOptionalProperty 一樣?

最後是 Readonly<Type> 的原始碼:

type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

也和剛剛我們提到的 ToReadOnlyProperty 相同。

範例程式碼

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

參考資料