TypeScript手冊翻譯系列2-接口

接口

TypeScript的一個核心原則是類型檢測重點放在值的形狀(shape),這有時候被稱爲鴨子類型化(duck typing)或結構子類型化(structural subtyping)。在TypeScript中,用接口(interfaces)來命名這些類型,來定義項目內部代碼的合約以及與外部代碼的契約。typescript

第一個接口

理解interface如何工做,最容易的方式就是先看一個簡單例子:
數組

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

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


當調用'printLabel'時類型檢測器開始檢查,'printLabel'函數有單個參數,要求傳入的對象有一個類型爲string,名爲'label'的屬性。注意這裏傳入的對象有多個屬性,但編譯器僅檢測所須要的屬性存在並且類型匹配便可。

能夠重寫上面的例子,但此次是用接口來描述要有一個類型爲string,名爲'label'的property

ide

interface LabelledValue {
  label: string;
}

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

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


interface 'LabelledValue'是描述前一個例子所須要的一個名字,它仍然表示要有一個類型爲string,名爲'label'的屬性。注意沒必要明確地給將這個接口的實現傳遞給'printLabel',這個與其餘語言相似。這裏重要的只是形狀(shape)。若是傳遞給函數的對象知足列出的需求,那麼就容許傳入。

須要指出的是類型檢測器不須要這些屬性按照某種方式排序,只要接口所需的屬性存在且類型匹配便可經過檢測。

函數

可選屬性

並不是須要一個接口中全部的屬性(properties)。只有在特定條件下一些屬性才存在,或者並不是存在全部的屬性。當建立相似於"option bags"模式時,可選屬性很廣泛,傳遞給函數的對象只有部分屬性被賦值。學習

下面是該模式的一個例子:
this

interface SquareConfig {
  color?: string;
  width?: number;
}

function createSquare(config: SquareConfig): {color: string; area: number} {  
  var newSquare = {color: "white", area: 100};  
  if (config.color) {
    newSquare.color = config.color;
  }  
  if (config.width) {
    newSquare.area = config.width * config.width;
  }  
  return newSquare;
}

var mySquare = createSquare({color: "black"});

有可選屬性的接口在編碼上與其餘接口相似,每一個可選屬性在屬性聲明時用一個 '?'來標記。
編碼

可選屬性的優勢是能夠描述可能存在的屬性,同時對那些未填充的屬性也會作類型檢測。例如假定傳遞給'createSquare'的屬性名稱拼寫錯誤,則會獲得下面錯誤消息:
spa

interface SquareConfig {
  color?: string;
  width?: number;
}

function createSquare(config: SquareConfig): {color: string; area: number} {  
  var newSquare = {color: "white", area: 100};  
  if (config.color) {
    newSquare.color = config.collor;  // Type-checker can catch the mistyped name here
  }  
  if (config.width) {
    newSquare.area = config.width * config.width;
  }  
  return newSquare;
}

var mySquare = createSquare({color: "black"});

函數類型

接口能夠描述JavaScript對象能夠接受的各類各樣的形狀(Shape)。 除了描述帶有屬性的對象,接口還能夠描述函數類型。
爲了用接口描述函數類型,給接口一個調用標記(call signature),相似於只給出參數列表和返回值的一個函數聲明。

.net

interface SearchFunc {
  (source: string, subString: string): boolean;
}


一旦定義,就能夠像其餘接口同樣來使用該函數類型接口。下面展現如何建立一個函數類型變量,將相同類型的一個函數值賦值給它。
翻譯

var mySearch: SearchFunc;

mySearch = function(source: string, subString: string) {  
  var result = source.search(subString);
  
  if (result == -1) {    
    return false;
  }  
  else {    
    return true;
  }
}


函數類型要可以經過類型檢測,不須要參數名稱保持一致。能夠將上面的例子寫爲:

var mySearch: SearchFunc;

mySearch = function(src: string, sub: string) {  
  var result = src.search(sub);
  
  if (result == -1) {    
    return false;
  }  
  else {    
    return true;
  }
}

函數參數被依次一個一個檢測,檢測每一個參數位置對應的類型是否匹配。並且這裏函數表達式的返回類型已經由返回值falsetrue暗示出。若是函數表達式返回的是numbers或strings,那麼類型檢測器將告警:返回類型與SearchFunc接口描述的返回類型不匹配。

數組類型

相似於如何利用接口來描述函數類型,接口也能夠描述數組類型。數組類型有一個描述對象索引的'index'類型,以及訪問索引對應的返回類型。

interface StringArray {
  [index: number]: string;
}

var myArray: StringArray;
myArray = ["Bob", "Fred"];

index能夠有兩種類型:string和number。能夠同時支持兩種index類型,但要求從numeric index返回的類型必須是從string index返回類型的子類型。

index標記功能的強大在於可描述數組和字典模式,還要求屬性都要匹配索引返回類型。在下面例子中,屬性沒有匹配索引返回類型,所以類型檢測器給出錯誤:

interface Dictionary {
  [index: string]: string;
  length: number;    // error, the type of 'length' is not a subtype of the indexer
}

Class類型

實現接口

在C#和Java等語言中接口最多見的一個用途是,明確強制類須要知足一個特定的契約,在TypeScript語言中一樣適用:

interface ClockInterface {
    currentTime: Date;
}

class Clock implements ClockInterface  {
    currentTime: Date;
    constructor(h: number, m: number) { }
}


接口中的方法也要在類中實現,就像下面例子中'setTime'方法:

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date);
}

class Clock implements ClockInterface  {
    currentTime: Date;
    setTime(d: Date) {        
      this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}


接口描述類的公開(Public)部分,而不包含私有部分。能夠據此來檢測類中也包含類實例私有部分的數據類型。

類的靜態部分與實例部分之間的差別

當使用類與接口時,要注意有兩種類型:靜態類型部分與實例類型部分(the type of the static side and the type of the instance side)。若是建立一個有構造函數標記的接口,而後試圖建立一個實現該接口的類時將獲得錯誤:

interface ClockInterface {    
  new (hour: number, minute: number);
}

class Clock implements ClockInterface  {
    currentTime: Date;
    constructor(h: number, m: number) { }
}


這是由於當類實現一個接口時,只檢測類的實例部分。因爲構造函數是在靜態部分,所以實例部分中沒有包含構造函數,當檢測時就報錯
這時,須要在類中直接實現靜態部分。在下面例子中直接使用類來實現靜態部分:

interface ClockStatic {    
  new (hour: number, minute: number);
}

class Clock  {
    currentTime: Date;
    constructor(h: number, m: number) { }
}

var cs: ClockStatic = Clock;
var newClock = new cs(7, 30);

擴展接口

與類很類似的是interfaces能夠擴展。這樣就能夠將一個接口中的成員拷貝到另外一個接口中,所以能夠將接口劃分爲更細的可重用的組件:

interface Shape {
    color: string;
}

interface Square extends Shape {
    sideLength: number;
}

var square = <Square>{};
square.color = "blue";
square.sideLength = 10;


一個接口能夠擴展多個接口,將這些接口組合在一塊兒:

interface Shape {
    color: string;
}

interface PenStroke {
    penWidth: number;
}

interface Square extends Shape, PenStroke {
    sideLength: number;
}

var square = <Square>{};
square.color = "blue";
square.sideLength = 10;
square.penWidth = 5.0;

混合類型

前面提到,接口能夠描述JavaScript中的許多類型。因爲JavaScript語言的動態和靈活性,可能遇到一個對象是上面多個類型的組合體。
在下面例子中的對象包含一個函數類型,一個對象類型,以及一些屬性:

interface Counter {
    (start: number): string;
    interval: number;
    reset(): void;
}

var c: Counter;
c(10);
c.reset();
c.interval = 5.0;


當與第三方JavaScript交互時,可能會用相似上面的模式來描述一個類型的完整形狀(shape)。


翻譯後記:

須要學習下 鴨子類型化(duck typing)、結構子類型化(structural subtyping)、"option bags"模式。

參考資料

[1] http://www.typescriptlang.org/Handbook#interfaces

[2] TypeScript - Interfaces, 破狼blog, http://greengerong.com/blog/2014/11/13/typescript-interfaces/

[3] TypeScript系列1-簡介及版本新特性, http://my.oschina.net/1pei/blog/493012

[4] TypeScript手冊翻譯系列1-基礎類型, http://my.oschina.net/1pei/blog/493181

相關文章
相關標籤/搜索