類型檢查專注於解析值所具有的"形態",這是TypeScript的核心原則之一。這個有時候被稱為"duck typing"或者"structural subtyping"。在TypeScript中,Interface中寫入這些類型的命名規范,並且也是一種強有力的方式來對你的代碼或者項目的外部代碼進行約束。
實現第一個接口
要看看interface怎么工作的最簡單的方式就是我們來寫一個例子:
function printLabel(labelledObj: {label: string}) {
console.log(labelledObj.label);
}
var myObj = {size: 10, label: "Size 10 Object"}; printLabel(myObj);
類型檢查器將會檢查調用的"printLabel"。printLabel函數需要有一個對象作為參數,並且這個對象有"label"屬性,該屬性值類型是string。注意,我們的對象其實有比這個更多的屬性,但編譯器只檢查當前匹配和需要的屬性的類型。
我們再寫一次上面的例子,只是這次我們使用interface來描述所需的"label"屬性值是一個字符串:
interface LabelledValue {
label: string;
}
function printLabel(labelledObj: LabelledValue) { console.log(labelledObj.label); } var myObj = {size: 10, label: "Size 10 Object"}; printLabel(myObj);
"LabelledValue"是一個名稱,我們用它來描述前面那個例子中的需求。它依然體現出需要一個字符串類型的並且名為"label"的屬性。注意,我們沒有明確的說明在其他語言中也是在"printLabel"函數傳入對象以實現此接口。這里,我們也僅僅是提下這個問題。如果我們傳遞給函數的對象符合要求,那么它就被允許了。
值的指出的是,類型檢查不需求這些屬性是來自挑選或者排序之后的,只需要interface所需的屬性是存在的,並且類型也是相對應的即可。
屬性可選
在interface中,並不是所有的屬性都是必需的。有些可能是在某個條件下才需要,不是一直需要用到。當用戶將一個對象傳入到一個只具有一對屬性的函數時(如創建一個"option bags"的方式),這些可選屬性是很方便的。
這里有個這種方式的例子:
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"});
可選屬性的interface和寫普通的interface相似,只是每個可選屬性用"?"符號標識,以作為申明這是可選的。
可選屬性的有點是,你可以描述這些可能會用到的屬性,同時還可以捕捉你明確了的那些不期望使用的屬性。舉例說明,我們為傳入的"createSquare"輸入了一個錯誤的屬性名稱,我們需要拋出一個錯誤信息來通知我們。
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; // 類型檢測器將會捕捉到你傳入了錯誤的名稱 } if (config.width) { newSquare.area = config.width * config.width; } return newSquare; } var mySquare = createSquare({color: "black"});
函數類型
interface能夠做和javascript的對象一樣的事:描述形態的范圍。除了描述一個對象的屬性外,interface還可以描述函數類型。
用interface描述一個函數的類型,我們需要給interface一個調用標識。這就像一個只有參數列表和返回值類型的函數聲明。
interface SearchFunc {
(source: string, subString: string): boolean; }
一旦定義后,我們就能和其他interface一樣使用它了。在這里,我們將展示如何創建一個函數類型的變量並且將其賦值為相同類型的的函數值。
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; } }
函數參數一次只檢查一個,相互檢查每個位置對應參數的類型。在這里,我們的函數表達式的返回類型被它返回的值所表明(這里的false和true)。假如函數表達式返回的是number或者string,那么類型檢查器將會通過返回類型與"SearchFunc" interface的描述類型不符用來警告我們。
數組類型
與我們使用interface來描述函數類型一樣,我們也可以用interface來描述數組類型。數組類型有一個"index"類型用來描述了數組索引的類型,以及一個返回值類型用來表示相對應索引的元素類型。
interface StringArray {
[index: number]: string;
}
var myArray: StringArray; myArray = ["Bob", "Fred"];
支持的兩種索引類型:string和number。數組可以同時使用這兩種索引類型。但也存在限制,數字索引的返回值類型必須是字符串索引返回值類型的子類型。
interface Something {
[index: string]: number;
[index: number]: number; //數字索引的返回值類型是number,它是字符串索引的返回值類型的子類型;若[index: string]:string,則數字索引的返回值類型也必須是string
}
雖然索引標識是個用來描述數組及"dictionary"模式的很好的方式,同時也迫使所有屬性需要匹配他們的返回類型。在這個例子中,屬性類型與索引類型不匹配,類型檢查程序給出錯誤:
interface Dictionary {
[index: string]: string;
length: number; // 錯誤, 'length'的類型不是索引類型的子類型
}
類類型
實現接口
在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) { } }
接口描述了類的公有部分,而非公有和私有兩部分。使用它們來檢查一個類的實例的私有部分所含的特殊類型是行不通的。
類的靜態部分和實例部分的差異
當處理類和接口的時候,你需要記住類有兩種類型:靜態的類型和實例的類型。你可能注意到了,如果你創建一個帶有構造簽名的接口並嘗試創建一個用來實現這個接口的類的時候,會產生一個錯誤:
interface ClockInterface {
new (hour: number, minute: number); } class Clock implements ClockInterface { currentTime: Date; constructor(h: number, m: number) { } } // 錯誤信息: Class 'Clock' incorrectly implements interface 'ClockInterface'
這是因為當用一個類來實現一個接口時,只檢查該類的實例部分。由於構造函數位於靜態部分,它不包含在該檢查中。
所以,我們需要直接來操作類的靜態部分。在這個例子中,我們直接與該類進行操作:
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);
接口擴展
和類一樣,接口也可以彼此間進行擴展。這個處理的任務是將一個接口的成員復制到另一個接口中,這樣就可以更自由地把接口分離成可復用的組件。
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; } function counter():Counter{ var self = <Counter>function(start:number){
self.interval = start; console.log("Hello World " + start); }; self.interval = 10; self.reset = function(){ self.interval = 10; } return self; } var c = counter();//c.interval = 10 c(5); //c.interval = 5 c.interval = 15; //c.interval = 15 c.reset(); //c.interval = 10
與JavaScript的第三方庫交互時,您可能需要使用上述模式來全面的定義類型。