前情提要
最近在看 TypeScript Wizard 的課程,稍作一下紀錄
1 | const user = { name: "John" }; |
這裡 IDE 會在 user.age 上報錯,因為 TypeScript 預設只允許存取「已知」的屬性。
由於 user 只有 name 屬性,TypeScript 會將其推斷為 { name: string } 類型,因此無法直接新增 age。
解法
這時候我們有幾種解法,但要挑選 最符合 TypeScript 精神的做法。
明確定義 User 類型
這是最推薦的作法
1 | type User = { name: string; age?: number }; |
使用 Record 定義動態物件
如果我們需要 允許物件擴展但仍保持類型安全,可以使用 Record<K, V>:
1 | const user: Record<string, string | number> = { name: "John" }; |
另一種動態的作法,明確定義索引簽名
1 |
|
這樣 user 可以擁有 任何鍵(string 類型),但值必須是 string 或 number,避免亂塞其他類型。
Immutable Objects
反之,如何保證物件的結構不會意外被修改?
我們可以使用 readonly 保持 Immutable Objects
1 | type User = Readonly<{ name: string; age: number }>; |
這種方式確保 user 一旦被賦值,就不能再改變,完全符合 Immutable Objects 的概念。
另一種方式是使用 Object.freeze() 來讓物件變成真正的 Immutable:
1 | const user = Object.freeze({ name: "John", age: 24 }); |
不過要注意,Object.freeze() 只會影響淺層屬性,深層的物件仍然可以變更。
進一步思考,如何選擇?
- 如果你的物件是固定結構但不可變更,readonly 是最佳選擇。
- 如果需要允許擴展但要控制類型範圍,使用 Record<K, V>。
- 如果想確保物件完全不可變,Object.freeze() 是最安全的選項。
參考
- Techniques for Solving Property Does Not Exist on Type Error
- Record 型別解釋:Record in TypeScript
- Object.freeze()
(fin)