iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 19
7
自我挑戰組

Typescript 初心者手札系列 第 19

【Day 19】TypeScript 介面(Interface) v.s. 型別別名(Type Alias)

昨天初步介紹了 TS 介面以及如何宣告,但昨天在研究介面的時候,心中一直有個疑惑,介面怎麼和之前提到的型別別名(Type Alias)有點像,究竟有什麼差別呢? 在深入探討介面實作之前,今天想先試著弄清楚這部分的觀念。

問題:使用 interface 和 type 的差異為何?

interface X {
    a: number
    b: string
}

type X = {
    a: number
    b: string
};

在搜尋資料時,找到幾篇覺得非常有參考價值的文章:

  1. Maxwell Alexius 大神的Day 13Day 16 鐵人文章
  2. stackoverflow 的回答
  3. dev.to 文章
  4. TYPESCRIPT INTERFACE VS. TYPE

一開始,我先從官網文件開始來看,官網提到介面和型別別名有以下的差異:

  1. 介面會創建新名稱,且任何地方都可以使用 ; 而型別別名不會創建新名稱。舉例來說:
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。

  1. 就程式碼的延展性來說,最好使用 Interface 勝於 type,但官網並沒有詳細解釋原因。

  2. 有些狀況無法使用 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;

應用比較

相同部分

1. 型別註記/型別檢測行為

不論是宣告 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 // 多了屬性,報錯
};
2. 介面和型別別名都可以表示物件格式,也都可以使用可選屬性(Optional Properties)、唯獨屬性(Read-only Properties)、任意屬性
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
}

相異部分

1. Interface 只能表示物件格式,無法表現原始型別、列舉、元組和複合型別; Type可以表現任何型別

根據 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 可以表示任何型別。

2. Interface 可以有多個相同名稱,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)沒有這個特性。

3. 擴展的作法有兩種:一種是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 使用情境比較如下:

  1. 單純想表示靜態格式資料概念時使用type,希望資料被重複多方利用時使用 interface
  2. 若是原始資料型別、列舉(Enum)和元組(Tuple)型別和複合型別,通常只能使用 type 進行宣告
  3. 若是物件格式 Interface 和 Type 都可以進行宣告,但建議使用 Interface 比較彈性
  4. Interface 和 Type 可以混用擴展,但使用 extends 和 union 或 intersection 擴展代表的含義不同:
    • 不希望再被擴充或靜態的型別格式就應該用 type 宣告 type,藉由 union 或 intersection 達成擴展
    • 希望之後被擴充或多方利用,則應該宣告成 interface,藉由 extends 去達成擴展

小結

今天花了一些時間去釐清整理 interface 和 type 使用的差異,雖然可能還有地方需要補強,但對於兩者的使用又更熟悉了些。明天繼續來探討類別(Class)!

如果有整理或觀念有誤,還請留言告知,我會盡快修正,謝謝


上一篇
【Day 18】TypeScript 資料型別 - 介面(Interface)宣告與屬性
下一篇
【Day 20】TypeScript 資料型別 - 類別(Class)
系列文
Typescript 初心者手札30

尚未有邦友留言

立即登入留言