【區分】Typescript 中 interface 和 type

ESnail發表於2019-07-23

在接觸 ts 相關程式碼的過程中,總能看到 interface 和 type 的身影。只記得,曾經遇到 type 時不懂查閱過,記得他們很像,相同的功能用哪一個都可以實現。但最近總看到他們,就想深入的瞭解一下他們。

interface:介面

TypeScript 的核心原則之一是對值所具有的結構進行型別檢查。 而介面的作用就是為這些型別命名和為你的程式碼或第三方程式碼定義契約。

interface LabelledValue {
  label: string;
}

function printLabel(labelledObj: LabelledValue) {
  console.log(labelledObj.label);
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);

介面就好比一個名字,用來描述上面例子裡的要求。

介面具有的特性:

  • 可選屬性
interface SquareConfig {
  color?: string;
}
  • 只讀屬性
interface Point {
    readonly x: number;
}
  • 多餘屬性檢查,防止使用不屬於介面的屬性
interface Preson {
    name: string;
    age?: number;
}

let p1:Person = {name: '小明'} // 正確
let p2:Person = {name: '小明', age: 18, sex: '男'}; // 報錯

// 繞過:多餘屬性不報錯
// 方式1 
let p = {name: '小明', age: 18, sex: '男'};
let p3 = p;

// 方式2
interface Preson {
    name: string;
    age?: number;
    [propName: string]: any
}
let p4 = {name: '小明', age: 18, sex: '男'};
  • 函式型別
interface SearchFunc {
  (source: string, subString: string): boolean;
}
  • 索引型別: 針對陣列
interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];
  • 類型別
    • 類實現介面
    interface ClockInterface {
      currentTime: Date;
      setTime(d: Date);
    }
    
    class Clock implements ClockInterface {
      currentTime: Date;
      setTime(d: Date) {
          this.currentTime = d;
      }
      constructor(h: number, m: number) { }
    }
    • 介面繼承介面,可多個
    interface Shape {
    color: string;
    }
    
    interface PenStroke {
        penWidth: number;
    }
    
    interface Square extends Shape, PenStroke {
        sideLength: number;
    }
    
    let square = <Square>{};
    square.color = "blue";
    square.sideLength = 10;
    square.penWidth = 5.0;

type:型別別名

type 會給一個型別起個新名字。 type 有時和 interface 很像,但是可以作用於原始值(基本型別),聯合型別,元組以及其它任何你需要手寫的型別。

舉例:

type Name = string; // 基本型別
type NameResolver = () => string; // 函式
type NameOrResolver = Name | NameResolver; // 聯合型別

function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n;
    } else {
        return n();
    }
}

起別名不會新建一個型別 - 它建立了一個新 名字來引用那個型別。給基本型別起別名通常沒什麼用,儘管可以做為文件的一種形式使用。

同介面一樣,型別別名也可以是泛型 - 我們可以新增型別引數並且在別名宣告的右側傳入:

type Container<T> = { value: T };

也可以使用型別別名來在屬性裡引用自己:

type Tree<T> = {
    value: T;
    left: Tree<T>;
    right: Tree<T>;
}

與交叉型別一起使用,我們可以建立出一些十分稀奇古怪的型別。

type LinkedList<T> = T & { next: LinkedList<T> };

interface Person {
    name: string;
}

var people: LinkedList<Person>;
var s = people.name;
var s = people.next.name;
var s = people.next.next.name;
var s = people.next.next.next.name;

然而,型別別名不能出現在宣告右側的任何地方。

type Yikes = Array<Yikes>; // error

interface vs type

1. Objects / Functions

兩者都可以用來描述物件或函式的型別,但是語法不同。

Interface

interface Point {
  x: number;
  y: number;
}

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

Type alias

type Point = {
  x: number;
  y: number;
};

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

2. Other Types

與介面不同,型別別名還可以用於其他型別,如基本型別(原始值)、聯合型別、元組。

// primitive
type Name = string;

// object
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };

// union
type PartialPoint = PartialPointX | PartialPointY;

// tuple
type Data = [number, string];

// dom
let div = document.createElement('div');
type B = typeof div;

3. Extend

兩者都可以擴充套件,但是語法又有所不同。此外,請注意介面和型別別名不是互斥的。介面可以擴充套件型別別名,反之亦然。

Interface extends interface

interface PartialPointX { x: number; }
interface Point extends PartialPointX { y: number; }

Type alias extends type alias

type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; };

Interface extends type alias

type PartialPointX = { x: number; };
interface Point extends PartialPointX { y: number; }

Type alias extends interface

interface PartialPointX { x: number; }
type Point = PartialPointX & { y: number; };

4. class Implements

類可以以相同的方式實現介面或型別別名。但是請注意,類和介面被認為是靜態的。因此,它們不能實現/擴充套件命名聯合型別的型別別名。

interface Point {
  x: number;
  y: number;
}

class SomePoint implements Point {
  x: 1;
  y: 2;
}

type Point2 = {
  x: number;
  y: number;
};

class SomePoint2 implements Point2 {
  x: 1;
  y: 2;
}

type PartialPoint = { x: number; } | { y: number; };

// FIXME: can not implement a union type
class SomePartialPoint implements PartialPoint {
  x: 1;
  y: 2;
}

5. extends class

類定義會建立兩個東西:類的例項型別和一個建構函式。 因為類可以建立出型別,所以你能夠在允許使用介面的地方使用類。

class Point {
    x: number;
    y: number;
}

interface Point3d extends Point {
    z: number;
}

6. Declaration merging

與型別別名不同,介面可以定義多次,並將被視為單個介面(合併所有宣告的成員)。

// These two declarations become:
// interface Point { x: number; y: number; }
interface Point { x: number; }
interface Point { y: number; }

const point: Point = { x: 1, y: 2 };

7. 計算屬性,生成對映型別

type 能使用 in 關鍵字生成對映型別,但 interface 不行。

語法與索引簽名的語法型別,內部使用了 for .. in。 具有三個部分:

  • 型別變數 K,它會依次繫結到每個屬性。
  • 字串字面量聯合的 Keys,它包含了要迭代的屬性名的集合。
  • 屬性的結果型別。
type Keys = "firstname" | "surname"

type DudeType = {
  [key in Keys]: string
}

const test: DudeType = {
  firstname: "Pawel",
  surname: "Grzybek"
}

// 報錯
//interface DudeType2 {
//  [key in keys]: string
//}

7. 其他細節

export default interface Config {
  name: string
}

// export default type Config1 = {
//   name: string
// }
// 會報錯

type Config2 = {
    name: string
}
export default Config2

總結

interface 和 type 很像,很多場景,兩者都能使用。但也有細微的差別:

  • 型別:物件、函式兩者都適用,但是 type 可以用於基礎型別、聯合型別、元祖。
  • 同名合併:interface 支援,type 不支援。
  • 計算屬性:type 支援, interface 不支援。

總的來說,公共的用 interface 實現,不能用 interface 實現的再用 type 實現。主要是一個專案最好保持一致。

參考:

相關文章