接口和類型別名很是類似,在大多狀況下兩者能夠互換。在寫TS的時候,想必你們都問過本身這個問題,我到底應該用哪一個呢?但願看完本文會給你一個答案。知道何時應該用哪一個,首先應該瞭解兩者之間的相同點和不一樣點,再作出選擇。git
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;
二者的擴展方式不一樣,但並不互斥。接口能夠擴展類型別名,同理,類型別名也能夠擴展接口。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 }
類型別名的右邊能夠是任何類型,包括基本類型、元祖、類型表達式(&
或|
等類型運算符);而在接口聲明中,右邊必須爲結構。例如,下面的類型別名就不能轉換成接口:ide
type A = number type B = A | string
擴展接口時,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 }
接口能夠定義屢次,屢次的聲明會合並。可是類型別名若是定義屢次,會報錯。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
。
這個例子能夠同時解釋譯文中第二個和最後一個不一樣點。
有的同窗可能會問,若是我不須要組合只是單純的定義類型的時候,是否是就能夠隨便用了。可是爲了代碼的可擴展性,建議仍是優先使用接口。如今不須要,誰能知道後續需不須要呢?因此,讓咱們大膽的使用接口吧~