這是我們今天要聊的內容,老樣的,如果你已經可以輕鬆看懂,歡迎直接左轉去看同事 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