TypeScript Interface vs Type知多少

接口和類型別名很是類似,在大多狀況下兩者能夠互換。在寫TS的時候,想必你們都問過本身這個問題,我到底應該用哪一個呢?但願看完本文會給你一個答案。知道何時應該用哪一個,首先應該瞭解兩者之間的相同點和不一樣點,再作出選擇。git

接口 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. 均可以擴展

二者的擴展方式不一樣,但並不互斥。接口能夠擴展類型別名,同理,類型別名也能夠擴展接口。github

接口的擴展就是繼承,經過 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. 類型別名更通用(接口只能聲明對象,不能重命名基本類型)

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

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將盡其所能把擴展和被擴展的類型組合在一塊兒,而不會拋出編譯時錯誤。ui

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

3. 屢次定義時表現不一樣

接口能夠定義屢次,屢次的聲明會合並。可是類型別名若是定義屢次,會報錯。code

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'.
}

到底應該用哪一個

若是接口和類型別名都能知足的狀況下,到底應該用哪一個是咱們關心的問題。感受哪一個均可以,可是強烈建議你們只要能用接口實現的就優先使用接口,接口知足不了的再用類型別名。orm

爲何會這麼建議呢?其實在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

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

結論

有的同窗可能會問,若是我不須要組合只是單純的定義類型的時候,是否是就能夠隨便用了。可是爲了代碼的可擴展性,建議仍是優先使用接口。如今不須要,誰能知道後續需不須要呢?因此,讓咱們大膽的使用接口吧~

相關文章
相關標籤/搜索