昨天初步介紹了 TS 介面以及如何宣告,但昨天在研究介面的時候,心中一直有個疑惑,介面怎麼和之前提到的型別別名(Type Alias)有點像,究竟有什麼差別呢? 在深入探討介面實作之前,今天想先試著弄清楚這部分的觀念。
問題:使用 interface 和 type 的差異為何?
interface X {
a: number
b: string
}
type X = {
a: number
b: string
};
在搜尋資料時,找到幾篇覺得非常有參考價值的文章:
一開始,我先從官網文件開始來看,官網提到介面和型別別名有以下的差異:
type Alias = { num: number }
interface Interface {
num: number;
}
declare function aliased(arg: Alias): Alias;
declare function interfaced(arg: Interface): Interface;
上面的程式碼由於型別別名不會創建新名稱,因此,interfaced 會回傳Interface(新名稱),而 aliased 函式會回傳 { num: number },而非 Alias。
就程式碼的延展性來說,最好使用 Interface 勝於 type,但官網並沒有詳細解釋原因。
有些狀況無法使用 Interface,必須使用 union 或 tuple 型別,則使用 type。
老實說,官網寫得不清不楚的,兩者的使用情境差異也不太清楚,甚至看到[stackoverflow]提到官網的文件是 Outdated 的狀況(這樣新手怎麼入坑啦(哭)),因此,參考了幾個前輩的文章觀念補充整理!
型別別名使用 type
關鍵字; 介面宣告使用 interface
關鍵字
type I = {
P : T
}
interface I{
P : T
}
舉例來說,型別別名和介面都可以表示物件/函式,寫法差異如下:
//使用 interface 表示物件
interface Point {
x: number;
y: number;
}
//使用 type 表示物件
type Point = {
x: number;
y: number;
};
//使用 interface 表示函式
interface SetPoint {
(x: number, y: number): void;
}
//使用 type 表示物件
type SetPoint = (x: number, y: number) => void;
不論是宣告 type 或 interface ,TS 的型別檢查基本上行為都一樣。倘若某變數A被註記為型別 T 或 介面 I,只要變數 A 少或多沒有在型別 T 或介面 I 中規範的屬性,又或者屬性值的型別不一致,TS 都會報錯!
//使用介面
interface PersonInfo {
name: string;
age: number;
}
//使用型別別名
type PersonInfo {
name: string;
age: number;
}
//少或多屬性,或屬性值型別無法對應都會報錯
const Una: PersonInfo = {
name: "Una",
age: '31', // 對應型別錯誤,報錯
gender: girl // 多了屬性,報錯
};
type PersonInfo ={
name: string;
age: number;
gender? : string
readonly phone: string
[x: string]: any
}
interface PersonInfo {
name: string;
age: number;
gender? : string
readonly phone: string
[x: string]: any
}
根據 TS 的語言規範說明:
Unlike an interface declaration, which always introduces a named object type, a type alias declaration can introduce a name for any kind of type, including primitive, union, and intersection types.
加上參考 Maxwell Alexius大神 的文章,介面表示物件格式(包含一般物件、函式、陣列、類別與類別建構的物件),但無法描述原始型別(Primitive)
、列舉(Enum)
、元組(Tuple)
和複合型別
,而 type 可以表示任何型別。
介面有一個特性是可以被重複定義多次,而此介面最後推論結果會是所有重複定義介面的交集,這件事的關鍵字叫做Declaration Merging
,舉例來說:
interface Box {
height: number;
width: number;
}
interface Box {
scale: number;
}
let box: Box = {height: 5, width: 6, scale: 10};
同樣名稱為 Box 的 interface 可以宣告多次,但若屬性有重複的話,型別必須彼此相符,否則會報錯。而型別別名(type alias)沒有這個特性。
extends
,只有 Interface 可以使用 ; 另一種作法是 union 或 intersection , Type 和 Interface 都可以使用。關於擴展的定義,網路上有多種說法。就目前看完的資料而言,我比較贊同 Type 和 Interface 都可以進行擴展,只是使用extends
關鍵字和複合型別所代表的擴展含義不同。
舉例來說:假設宣告某型別 T1 與別 T2 ,並使用複合型別宣告新的型別別名C
type C = T1 & T2
則代表的含義是「定義新的型別別名C,其型別的靜態格式為型別T1的內容和型別 T2 的內容」。
倘若使用extends
關鍵字,宣告某型別 T1 與某介面 I1,並宣告新介面 I2(其中 T1 不是原始型別,否則會報錯):
interface I2 extends T, I1{}
則代表的含義是「宣告新的介面T2,任何使用介面 T2 的物件實例都必須實踐出型別 T1 的靜態屬性和介面 I1的所有屬性和方法」
另外,和 Interface 和 Type 的擴展可以混,例如:
Interface 擴展 Interface
interface A { x: number; }
interface B extends A { y: number; }
Type 擴展 Type
type A = { x: number; };
type B = A & { y: number; };
Interface 擴展 Type
type A = { x: number; };
interface B extends A { y: number; }
Type 擴展 Interface
interface A { x: number; }
type B = A & { y: number; };
推薦Maxwell Alexius 大神的鐵人賽文章,整理的非常詳細! Interface 和 Type 使用情境比較如下:
今天花了一些時間去釐清整理 interface 和 type 使用的差異,雖然可能還有地方需要補強,但對於兩者的使用又更熟悉了些。明天繼續來探討類別(Class)!
如果有整理或觀念有誤,還請留言告知,我會盡快修正,謝謝