沐鳴平台網址_TypeScript Interface vs Type知多少

接口和類型別名非常相似,在大多情況下二者可以互換。在寫TS的時候,想必大家都問過自己這個問題,我到底應該用哪個呢?希望看完本文會給你一個答案。知道什麼時候應該用哪個,首先應該了解二者之間的相同點和不同點,再做出選擇。

接口 vs 類型別名 相同點

1. 都可以用來描述對象或函數

interface Point {
  x: number
  y: number
}

interface SetPoint {
  (x: number, y: number): void;
}
type Point = {
  x: number;
  y: number;
};

type SetPoint = (x: number, y: number) => void;

2. 都可以擴展

兩者的擴展方式不同,但並不互斥。接口可以擴展類型別名,同理,類型別名也可以擴展接口。

接口的擴展就是繼承,通過 extends 來實現。類型別名的擴展就是交叉類型,通過 & 來實現。

// 接口擴展接口
interface PointX {
    x: number
}

interface Point extends PointX {
    y: number
}
// 類型別名擴展類型別名
type PointX = {
    x: number
}

type Point = PointX & {
    y: number
}
// 接口擴展類型別名
type PointX = {
    x: number
}
interface Point extends PointX {
    y: number
}
// 類型別名擴展接口
interface PointX {
    x: number
}
type Point = PointX & {
    y: number
}

接口 vs 類型別名不同點

1. 類型別名更通用(接口只能聲明對象,不能重命名基本類型)

類型別名的右邊可以是任何類型,包括基本類型、元祖、類型表達式(&或|等類型運算符);而在接口聲明中,右邊必須為結構。例如,下面的類型別名就不能轉換成接口:

type A = number
type B = A | string

2. 擴展時表現不同

擴展接口時,TS將檢查擴展的接口是否可以賦值給被擴展的接口。舉例如下:

interface A {
    good(x: number): string,
    bad(x: number): string
}
interface B extends A {
    good(x: string | number) : string,
    bad(x: number): number // Interface 'B' incorrectly extends interface 'A'.
                           // Types of property 'bad' are incompatible.
                           // Type '(x: number) => number' is not assignable to type '(x: number) => string'.
                           // Type 'number' is not assignable to type 'string'.
}

但使用交集類型時則不會出現這種情況。我們將上述代碼中的接口改寫成類型別名,把 extends 換成交集運算符 &,TS將盡其所能把擴展和被擴展的類型組合在一起,而不會拋出編譯時錯誤。

type A = {
    good(x: number): string,
    bad(x: number): string
}
type B = A & {
     good(x: string | number) : string,
     bad(x: number): number 
}

3. 多次定義時表現不同

接口可以定義多次,多次的聲明會合併。但是類型別名如果定義多次,會報錯。

interface Point {
    x: number
}
interface Point {
    y: number
}
const point: Point = {x:1} // Property 'y' is missing in type '{ x: number; }' but required in type 'Point'.

const point: Point = {x:1, y:1} // 正確
type Point = {
    x: number // Duplicate identifier 'A'.
}

type Point = {
    y: number // Duplicate identifier 'A'.
}

到底應該用哪個

如果接口和類型別名都能滿足的情況下,到底應該用哪個是我們關心的問題。感覺哪個都可以,但是強烈建議大家只要能用接口實現的就優先使用接口,接口滿足不了的再用類型別名。

為什麼會這麼建議呢?其實在TS的wiki中有說明。具體的文章地址在這裏。

以下是Preferring Interfaces Over Intersections的譯文:

大多數時候,對於聲明一個對象,類型別名和接口表現的很相似。

interface Foo { prop: string }

type Bar = { prop: string };

然而,當你需要通過組合兩個或者兩個以上的類型實現其他類型時,可以選擇使用接口來擴展類型,也可以通過交叉類型(使用 & 創造出來的類型)來完成,這就是二者開始有區別的時候了。

  • 接口會創建一個單一扁平對象類型來檢測屬性衝突,當有屬性衝突時會提示,而交叉類型只是遞歸的進行屬性合併,在某種情況下可能產生 never 類型
  • 接口通常表現的更好,而交叉類型做為其他交叉類型的一部分時,直觀上表現不出來,還是會認為是不同基本類型的組合
  • 接口之間的繼承關係會緩存,而交叉類型會被看成組合起來的一個整體
  • 在檢查一個目標交叉類型時,在檢查到目標類型之前會先檢查每一個組分

上述的幾個區別從字面上理解還是有些繞,下面通過具體的列子來說明。

interface Point1 {
    x: number
}

interface Point extends Point1 {
    x: string // Interface 'Point' incorrectly extends interface 'Point1'.
              // Types of property 'x' are incompatible.
              // Type 'string' is not assignable to type 'number'.
}
type Point1 = {
    x: number
}

type Point2 = {
    x: string
}

type Point = Point1 & Point2 // 這時的Point是一個'number & string'類型,也就是never

從上述代碼可以看出,接口繼承同名屬性不滿足定義會報錯,而相交類型就是簡單的合併,最後產生了 number & string 類型,可以解釋譯文中的第一點不同,其實也就是我們在不同點模塊中介紹的擴展時表現不同。

再來看下面例子:

interface PointX {
    x: number
}

interface PointY {
    y: number
}

interface PointZ {
    z: number
}

interface PointXY extends PointX, PointY {
}

interface Point extends PointXY, PointZ {
   
}
const point: Point = {x: 1, y: 1} // Property 'z' is missing in type '{ x: number; y: number; }' but required in type 'Point'
type PointX = {
    x: number
}

type PointY = {
    y: number
}

type PointZ = {
    z: number
}

type PointXY = PointX & PointY

type Point = PointXY & PointZ

const point: Point = {x: 1, y: 1} // Type '{ x: number; y: number; }' is not assignable to type 'Point'.
                                  // Property 'z' is missing in type '{ x: number; y: number; }' but required in type 'Point3'.

從報錯中可以看出,當使用接口時,報錯會準確定位到Point。
但是使用交叉類型時,雖然我們的 Point 交叉類型是 PointXY & PointZ, 但是在報錯的時候定位並不在 Point 中,而是在 Point3 中,即使我們的 Point 類型並沒有直接引用 Point3 類型。

如果我們把鼠標放在交叉類型 Point 類型上,提示的也是 type Point = PointX & PointY & PointZ,而不是 PointXY & PointZ。

這個例子可以同時解釋譯文中第二個和最後一個不同點。

結論

有的同學可能會問,如果我不需要組合只是單純的定義類型的時候,是不是就可以隨便用了。但是為了代碼的可擴展性,建議還是優先使用接口。現在不需要,誰能知道後續需不需要呢?所以,讓我們大膽的使用接口吧~

來源:https://segmentfault.com/a/1190000039834284

站長推薦

1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

鏈接: http://www.fly63.com/article/detial/10220