詳解TypeScript中的接口interface(1)

TS中的接口,即 interface ,既可以作為數據類型來使用,也可以當做其它語言中可以被類繼承的接口來使用。下面我們先來介紹接口作為類型使用的特性。

typescript中的代碼都可以在這裡運行:

https://www.typescriptlang.org/play/index.html

定義和使用

TypeScript中,聲明變量時需要指定變量的類型,基本類型有 string 、 number 等,但是對於包含特定屬性的對象,基本類型是無法滿足要求的。

比如,我們希望用一個對象來表示動物,這個對象需要含有下面三個屬性:

  • name :表示動物的名字
  • food :表示喜歡吃的食物
  • weight : 表示大致體重

我們可以定義一個名為 Animal 的接口,然後指定它的屬性的類型:

<code>interface Animal {
name: string;
food: string;
weight: number;
}/<code>

然後我們可以用Animal 來指定變量的類型:

<code>let monkey: Animal = { name: '猴子', food: '香蕉', weight: 100 }/<code>

如果賦值為一個對象,裡面缺少某個屬性,則會報錯:

<code>let monkey: Animal = { name: '猴子', food: '香蕉' }/<code>
詳解TypeScript中的接口interface(1)

可選屬性

我們可以給Animal擴展一些類型,以應對不同的動物。比如,人作為一種動物,可以有國家的屬性, 而猴子作為一種動物,沒有國家的屬性,但是可以有類別,比如金絲猴、狒狒等。對於某些動物有,另外一些動物沒有的屬性,我們可以定義為可選屬性,即用一個 ? 來表示:

<code>interface Animal {
name: string;
food: string;
weight: number;
country?: string;
breed?: string;
}/<code>

這樣,即使對象中沒有可選屬性,也不會報錯:

<code>let person: Animal = { 
name: '人',
food: '啥都吃',
weight: 150,
country: 'China'
}
let monkey: Animal = {
name: '猴子',
food: '香蕉',
weight: 100,
breed: '金絲猴'
}/<code>


只讀屬性

可以將接口的某些屬性指定為只讀類型,這樣就無法更改其中的屬性了。比如我們可以把 name 指定為只讀:

<code>interface Animal {
readonly name: string;
food: string;
weight: number;
country?: string;
breed?: string;
}/<code>

這時再去改變屬性值會報錯:

詳解TypeScript中的接口interface(1)


根據屬性名定義屬性類型

世界上的動物有很多種,必須用很多個屬性來形容動物,所以 Animal 應該還可以有其它很多種屬性,比如性別啊、棲息環境啊等等,但是我們不可能一一列舉出所有屬性,一是因為代碼太多了,二是我們也不知道到底有多少種屬性,所以我們希望能用一個比較抽象的方式來表示 Animal 接口中還有其它屬性。

TypeScript提供了這種功能,讓我們指定任意屬性值:

<code>interface Animal {
name: string;
food: string;
weight: number;
country?: string;
breed?: string;
[propName:string]: any;
}/<code>

我們使用了 [propName:string]: any ,來表明在Animal類型的數據中,對象可以包含任何字符串類型的key,並且對應key的值可以是任何類型。

有了上述聲明, Animal 類型的對象就可以添加任何屬性了,只要該屬性是字符串類型的。我們可以稱這種聲明為

簽名式屬性聲明


使用限制

簽名式屬性聲明有2個限制,其一是接口中其它顯式聲明的屬性的類型必須是簽名式聲明的類型的子集。也就是說,如果有了如下聲明: [propName:string]:number ,那麼接口中其它 key 為 string 類型的屬性,取值都必須是 number 類型。

例如下面的代碼報錯:

詳解TypeScript中的接口interface(1)

因為 weight 很明顯是一個 string 類型的key,但是它的取值類型為 number ,這就和 [propName:string]:string 衝突了。必須改用下面的寫法:

<code>interface Animal {
name: string;
food: string;
weight: number;
[propName: string]: string | number;
}/<code>

string | number 是一種聯合類型,表明該類型可以是 string 或 number , number 當然是其子集。


另一個限制簽名式屬性聲明可選屬性會發生衝突。例如下面的代碼:

<code>interface Animal {
name: string;
food: string;
weight: number;
country?: string;
[propName: string]: string | number;
}
// 會報錯,提示country 取值為undefined | string, 和 string | number 不兼容/<code>

因為已經聲明瞭 country 是可選屬性,也就是說 country 的屬性值是 undefined | string

類型,這種類型顯然是和 string | number 是不兼容的。


解法有兩個:

  • 去掉可選屬性 country 。
  • [propName: string] 增加一種取值類型,即 string | number | undefined
<code>interface Animal {
name: string;
food: string;
weight: number;
country?: string;
[propName: string]: string | number | undefined;
}/<code>


多餘類型檢查

默認情況下,如果給某個類型的值傳了多餘的屬性,TS編譯器會報錯,比如多傳了一個 ability 屬性:

<code>let monkey: Animal = { 
name: '猴子',
food: '香蕉',
weight: 100,
breed: '金絲猴',

ability: '爬樹',
}/<code>

報錯提示 Animal 不存在 ability 屬性:

詳解TypeScript中的接口interface(1)

所以我們儘量應該按照接口定義的數據格式去賦值,不然會報錯。


有3種方法可以避免這種報錯,下面分別來講解。

1. 類型斷言

我們可以用 as 操作符,明確告訴TS,我們給出的某個值就是另一種類型,不必再報錯了,如下:

<code>interface Animal {
name: string;
food: string;
weight: number;
country?: string;
[propName: string]: string | number | undefined;
}
let monkey2: Animal = {
name: '猴子',
food: '香蕉',
weight: 100,
breed: '金絲猴',
ability: '爬樹'
} /<code>

再舉個栗子:

<code>function consoleAnimal(a: Animal) {
console.log(a.name);
}
consoleAnimal({ name: '猴子', food: '香蕉', weight: 100, ability: '爬樹' });/<code>

這時候會報錯,告訴你傳給函數的參數不符合 Animal 接口的規定,但是我們可以用 as 操作符來將參數指定為 Animal 類型, 如下,就不會再報錯了:

<code>function consoleAnimal(a: Animal) {
console.log(a.name);
}
const aMonkey2 = { name: '猴子', food: '香蕉', weight: 100, ability: '爬樹' };
consoleAnimal(aMonkey2);/<code>

如前文所述,給 Animal 增加 字符串類型的屬性值,並指定取值類型為 any 既可任意添加屬性:

<code>interface Animal {
name: string;
food: string;
weight: number;
country?: string;
[propName: string]: string | number | undefined;
}
let monkey2: Animal = {
name: '猴子',
food: '香蕉',
weight: 100,
breed: '金絲猴',
ability: '爬樹'
} /<code>

3. 通過中間變量傳遞值能跳過TS的檢查

我們先將值賦值給一箇中間變量,再把中間變量賦值給 我們想要用的變量,既可跳過TS的檢查。如下例子:

<code>interface Animal {
name: string;
food: string;
weight: number;
country?: string;
}
const aMonkey = {
name: '猴子',
food: '香蕉',
weight: 100,
breed: '金絲猴',
ability: '爬樹'
};
let monkey2: Animal = aMonkey;/<code>

函數傳值也可以使用該方法:

<code>function consoleAnimal(a: Animal) {
console.log(a.name);

}
const aMonkey2 = { name: '猴子', food: '香蕉', weight: 100, ability: '爬樹' };
consoleAnimal(aMonkey2);/<code>


使用接口規定函數類型

我們也可以使用接口來定義一個函數的格式,語法如下:

<code>interface FuncName {
(parameter1: string, parameter2: number): boolean;
}/<code>

即定義函數的參數列表參數類型返回值類型,可以看出,上述代碼有兩個參數,分別為 string 類型和 number 類型,返回值為 boolean 類型。

如果指定某個函數的類型為該接口類型,該函數的定義就必須符合條件:

<code>let mySearch: FuncName;
mySearch = function(para1: string, para2: number): boolean {
return para1.slice(para2).length > 4;
};/<code>

從上面的代碼可以看出,定義函數時的參數,不必一定要和接口定義中的參數名相同,只要位置相對應即可。

因為我們已經指定了變量為 FuncName 類型,所以如果在定義參數時,不寫參數類型和返回值類型,TS也能自動推斷出對應的類型。

<code>let mySearch: FuncName;
mySearch = function(para1, para2) {
return para1.slice(para2).length > 4;
};/<code>


分享到:


相關文章: