1. 程式人生 > >TypeScript躬行記(2)——介面

TypeScript躬行記(2)——介面

  在傳統的面嚮物件語言中,介面(Interface)好比協議,它會列出一系列的規則(即對行為進行抽象),再由類來實現這些規則。而TypeScript中的介面更加靈活,除了包含常規的作用之外,它還能擴充套件其它的類、為物件的型別命名以及約束值的結構等,大大消除了許多潛在的錯誤。

一、屬性

  TypeScript中的介面可通過宣告屬性和其型別來限制物件的結構。例如定義一個名為Person的介面,包含一個字串型別的name屬性和一個數字型別的age屬性,如下所示。

interface Person {
  name: string;
  age: number;
}

  當宣告一個Person型別的物件時,必須將兩個屬性都定義,並且型別也要與介面中的一致,如下所示。

let worker: Person = {
  name: "strick",
  age: 28
};

  一旦在worker物件中少定義某個介面中的屬性或多一個在介面中未宣告的屬性,那麼就會在編譯階段報錯。注意,TypeScript的型別檢查器不會比對屬性在介面和物件中的定義順序,只要名稱和型別匹配,就能編譯通過。

1)可選屬性

  TypeScript允許介面中的屬性定義為可選的,只要在屬性名後跟問號(?),就能變為可選屬性,如下所示。

interface Person {
  school?: string;
}

  可選屬性既能預定義可能需要的屬性,也能在捕獲沒有的屬性時給出帶有啟發作用的錯誤提示,例如在建立worker物件時,定義一個schools屬性(如下所示),在編譯時就會報"'schools' does not exist in type 'Person'. Did you mean to write 'school'?"的錯誤。

let worker: Person = {
  schools: "university"
};

  由此可知,在物件中定義一個未在介面中宣告的屬性仍然是不允許的。

2)只讀屬性

  如果要讓物件的某個屬性只能在建立時被賦值,那麼可以將readonly關鍵字作用於相應的介面屬性,使其變為只讀的,如下所示。

interface Person {
  readonly gender: string;
}

  由於gender是一個只讀屬性,因此不能在物件初始化後對其進行修改,如下所示。

let worker: Person = {    //正確
  gender: "男"
};
worker.gender = "女";     //錯誤

3)任意屬性

  當介面需要包含任意屬性時,可以通過索引的方式實現,如下所示,用方括號將索引名和索引型別包裹起來。

interface Person {
  [prop: string]: string;
}

  在使用Person型別時,可以傳任意多個字串型別的屬性,如下所示。

let worker: Person = {
  name: "strick",
  gender: "男"
};

  注意,一旦聲明瞭任意屬性之後,那麼必選屬性和可選屬性都得是其型別的子型別。在下面的示例中,由於可選的age屬性的型別是number,不是string的子型別,因此在編譯時會報錯。

interface Person {
  name: string;                //正確
  age?: number;                //錯誤
  [prop: string]: string;
}

  TypeScript除了支援字串型別的索引之外,還支援數字型別的索引,如下所示。

interface Person {
  [prop: number]: number;
}

  有一點需要注意,當在介面中同時定義字串和數字兩種型別的索引時,後者對應的值型別得是前者的子型別。因為這個原因,導致下面的程式碼無法在編譯時通過。

interface Person {
  [prop: string]: string;
  [prop: number]: number;            //錯誤
}

  TypeScript之所以如此限制,是因為JavaScript會將數字自動轉換成字串後再去索引物件,例如用10和“10”兩個值去索引,得到的結果是一樣的,所以兩種索引對應的值型別要保持一致。

二、繼承

1)類繼承介面

  與C#、Java等面嚮物件語言一樣,TypeScript中的類也能繼承介面,並且介面中的成員會讓類強制實現。有了介面之後,它的任何更改都有可能導致編譯錯誤,從而就能保證相關程式碼的同步。下面通過一個示例來演示類繼承介面,首先建立一個名為Person的介面,包含name屬性和getName()方法,如下所示。

interface Person {
  name: string;
  getName(): string;
}

  然後再建立一個名為Member的類,通過implements關鍵字繼承Person介面,如下所示。在編譯時,一旦發現類中缺少介面的屬性或方法,就會馬上報錯。

class Member implements Person {
  name: string = "strick";
  getName() {
    return this.name;
  }
}

  類能繼承多個介面,只要在類中實現它的成員,就能編譯成功,如下所示,Member類繼承了Person和Profile兩個介面,限於篇幅原因,在其內部省略了name和getName()兩個成員的實現。

interface Profile {
  school: string;
}
class Member implements Person, Profile {
  school: string = "university";
}

  注意,類不能實現介面中的所有成員,例如在介面中定義一個構造器,再用一個類通過建構函式來實現這個介面,此時編譯將會失敗,程式碼如下所示。

interface Person {
  new (name: string);
}
class Member implements Person {
  constructor(name: string) { }
}

  類包含靜態和例項兩部分,由於編譯器只會對介面的例項部分進行型別檢查,而constructor()函式屬於類的靜態部分,因此會被忽略,從而導致無法在類中找到匹配的成員來實現介面。

  如果要實現介面中的構造器,那麼有兩種方式可供選擇。第一種是引數回撥,如下程式碼所示,Member類不再直接繼承Person介面,而是作為引數傳遞給createPerson()函式,並且其第一個引數被宣告為Person型別。

class Member {
  constructor(name: string) { }
}
function createPerson(ctor: Person, name: string) {
  return new ctor(name);
}
createPerson(Member, "strick");

  第二種是類表示式,如下程式碼所示,將Man變數宣告為Person型別,並把Member類賦給它。

let Man: Person = class Member {
  constructor(name: string) { }
}

2)介面繼承介面

  介面之間也可相互繼承,這樣既能更細粒度的分割介面,也能最大化的重用程式碼。與類不同的是,只需將其它的介面成員複製過來,而不必實現它們。在下面的示例中,Square介面通過extends關鍵字繼承了Shape介面。

interface Shape {
  background: string;
}
interface Square extends Shape {
  width: number;
}

  一個介面還可以繼承多個其它介面,創建出一個合成介面,如下所示,extends後面跟了Shape和Border兩個介面。

interface Border {
  color: string;
}
interface Ellipse extends Shape, Border {
  angle: string;
}

3)介面繼承類

  當介面繼承一個類時,它會繼承類的所有成員(包括私有和受保護的成員),但不會去實現它們。以下面的TextBox介面為例,它繼承了Control類。

class Control {
  private width: number;
  protected height: number;
}
interface TextBox extends Control {
  type: string;
}
class Tel implements TextBox {
  type: string = "tel";
}

  上例中的Tel類直接繼承了TextBox介面,雖然實現了介面中的type屬性,但仍然會報“Type 'Tel' is missing the following properties from type 'TextBox': width, height”的錯誤。因為Button介面繼承的width和height兩個屬性也需要實現。為了避免出現這些錯誤,可以通過Control的子類來實現TextBox介面,如下所示。

class Password extends Control implements TextBox {
  type: string = "password";
}

&n