
這是我們今天要聊的內容,老樣的,如果你已經可以輕鬆看懂,歡迎直接左轉去看同事 Kyle 的精彩文章 — 「今晚,我想來點 Web 前端效能優化大補帖!」。
在 Day14 我們理解了如何透過 Mapped Type 修改物件型別中屬性的 value,在 Day15 中我們則理解了如何透過 Mapped Type 來修改物件型別中屬性的 key,看起來好像已經能完整操作物件型別了,但等等,在 TypeScript 的物件型別中還有一個是 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
}
在知道 Property Modifiers 後,讓我們來看看如何透過 Mapped Types 來修改屬性的 Property Modifiers。
如果要在 Mapped Types 中添加或移除 Property Modifiers 的話(像是把所有的屬性都變成 optional),需要用到 + 或 -,預設沒寫的話就是 + ,來看下面幾個例子。
要改變物件屬性是否為 optional,只需要使用 +? 或 -? 即可。若要把每個物件型別的屬型都變 optional,可以使用 +? 或 ? 都可以,因為預設就會是 +:

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

實際上使用這兩個 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 的:

而 RemoveOptionalProperty 則能夠以此類推,使用 -? 後所有物件屬性原本的 optional 也都會被移除。
使用 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 來操作,不是它們不常被用到,而是因為 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