TypeScript 筆記 - Type 與 Interface 的選擇
前言
在我初次接觸 TypeScript 時,就面臨一個讓人困擾的問題:「到底什麼情況該用 Type?什麼情況該用 Interface?」這個看似簡單的選擇題,實際上卻困擾了我很長一段時間。兩者看起來可以實現相似的功能,卻又各有特色,常讓我猜測業界是否有某種不成文的規範或最佳實踐。這個疑問曾讓我在定義新型別時猶豫不決,甚至感到些許焦慮。我相信,這應該是許多 TypeScript 初學者共同的經歷。
隨著我參與的專案規模越來越大,這個困惑不僅沒有減輕,反而衍生出了更多考量。不只是功能需求的增加,連 TypeScript 的型別檢查(type checking)速度也成為一個不得不關注的重點。雖然型別系統帶來了安全性與維護性,但如果型別檢查的速度跟不上專案成長,開發體驗很快就會變得卡頓而痛苦。最近看到一篇很棒的文章 Type vs Interface: Which Should You Use?,剛好深入探討了這個議題,讓我有很多共鳴。因此想趁這個機會,把我的心得整理成筆記,一方面加深理解,一方面也希望能為其他開發者提供些許實用的指引。
Type 與 Interface 在效能上的差異
參考來源:Performance: Preferring interfaces over intersections
效能考量:Intersection (&) vs Interface extends
- TypeScript 編譯器在處理
interface extends時,效能會比處理type & type來得更好。 - 使用
interface extends:- interface 之間的關係可以被快取
- 因為 interface 在 extends 時,可以建立快取(cache),所以相同的型別推斷可以重複利用,進一步提升整體速度,特別是在大型專案或複雜繼承結構中效果明顯。
- 使用
type A = B & C:- 編譯器需要額外計算交集結果,每次推斷都必須重新展開與比較,無法像 interface 那樣做快取,因此在型別量一大時會顯著拖慢速度。
小結:如果考量效能,建議盡可能以
interface extends的方式設計型別繼承結構,減少不必要的交集運算,可以有效讓專案保持較快的型別檢查速度。
使用 type 的小坑:Implicit Index Signature
什麼是 implicit index signature?
- implicit index signature 是指 TypeScript 在處理
type定義物件時,隱含允許物件擁有任意額外屬性的行為。 - 除非額外限制,否則一個以
type定義的物件可以包含原本型別中沒有定義的屬性,且不會報錯,這在某些情況下會導致型別安全性問題。
範例比較
使用 type:
使用 interface:
在這個例子中可以清楚看到,使用 interface 能夠強制物件結構符合預期,不允許隨意多出未定義的屬性,而 type 則比較寬鬆,這就是 implicit index signature 帶來的影響。
小結
- 使用
type定義物件時,需要特別小心,否則可能會因為 implicit index signature 而讓型別檢查失效,引入不必要的風險。 - 如果想要加強約束,使用
interface會是一個比較安全的選擇,尤其是面對需要嚴謹型別保證的業務場景。
使用 Interface 的小坑:Declaration Merging
在 TypeScript 裡,interface 有一個特性,那就是相同名稱的 Interface 會自動合併,有時候會很方便(!?),但是在專案規模較大的情況下可能會是惡夢
假設我在 ts 檔的最上方寫了:
另外我在同一個 ts 檔的尾端(可能隔了好幾百行)又有人寫了:
此時 TypeScript 會自動幫我們「合併」成:
看起來好像沒問題,但實際上,這意味著所有 Car 都要同時有 brand 和 startEngine,不小心就會踩坑。在大專案裡,我們可能有上千行程式碼。
最上面有個 interface Event { ... },最下面有人又寫了 interface Event { ... },兩個就被靜悄悄地合併。可能 debug 了 20 分鐘才發現,超級沮喪。
當然,如果擔心潛在風險,也可以開啟 ESLint 的 no-unsafe-declaration-merging 規則。雖然 TypeScript 編譯器本身允許這種合併,但有了這條規則,ESLint 就能在編譯前先提醒你,避免在開發過程中踩坑。
type vs interface:該使用哪一個?
我的想法
- 實際上,該使用哪一個,還是需要依照團隊的 coding style 來決定。
- 畢竟每個團隊的開發習慣、專案需求、技術偏好、以及歷史包袱都不太一樣,有些團隊會統一規定全部用
type,有些則傾向盡量用interface。
- 畢竟每個團隊的開發習慣、專案需求、技術偏好、以及歷史包袱都不太一樣,有些團隊會統一規定全部用
- 以我的話來說:
- 在定義物件型態時,我會選擇使用 interface:
- 可以避免 implicit index signature 的問題,讓型別結構更穩固可控。
- 雖然 interface 有宣告合併(Declaration Merging)的特性,有可能在大型專案中出現意料之外的型別擴展,但這個問題可以搭配 ESLint 的
no-redeclare規則來加以規範,減少風險。
- 在定義原始型別(primitive type)、聯集型別(union type)、或交集型別(intersection type)時,我會使用 type:
- 這類非物件型別的結構使用
type比較自然且直覺。
- 這類非物件型別的結構使用
- 在定義物件型態時,我會選擇使用 interface:
總結
interface和type本質上都是定義型別的工具,各有優缺點,沒有絕對的對錯或標準答案。- 最重要的是根據實際需求,以及團隊的共識來制定合理的使用規範。
- 如果專案偏向大型、需要高效能與型別安全,建議傾向使用
interface定義物件結構,使用type定義原始型別或複合型態。