你要知道的
TypeScript的核心原則之一是對(duì)值所具有的結(jié)構(gòu)進(jìn)行類(lèi)型檢查。接口的作用就是為類(lèi)型命名和為代碼或第三方代碼定義契約或者約束吗购。
接口
什么時(shí)候該使用接口呢,先看下面一個(gè)示例砸狞。
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label);
}
函數(shù)printLabel有一個(gè)參數(shù)捻勉,并且這個(gè)參數(shù)對(duì)象要有一個(gè)類(lèi)型為string名為label的屬性。我們傳入的對(duì)象參數(shù)實(shí)際上會(huì)包含很多屬性刀森,但是編譯器只會(huì)檢查那些必需的屬性是否存在踱启,并且其類(lèi)型是否匹配。但有些時(shí)候ts沒(méi)有那么寬松,就會(huì)出現(xiàn)問(wèn)題禽捆。
如果這個(gè)約束使用接口笙什,如下:
interface LabelledValue {
label: string
}
function printLabel(labelledObj: LabelledValue) {
console.log(labelledObj.label);
}
let myObj = { size: 10, label: 'Size 10 Object' };
printLabel(myObj);
這個(gè)接口描述了傳入的參數(shù)對(duì)象要滿(mǎn)足的條件,只要這個(gè)參數(shù)對(duì)象含有一個(gè)名為label胚想,類(lèi)型為string的屬性即可琐凭。
區(qū)別于其他語(yǔ)言,這里只能說(shuō)是對(duì)象的類(lèi)型格式由接口來(lái)約束浊服,不能說(shuō)是對(duì)象實(shí)現(xiàn)了借口统屈。像上面說(shuō)的那樣,只要滿(mǎn)足條件即可牙躺,順序也不在檢查范圍內(nèi)愁憔。
可選屬性
接口里的屬性不全都是必需的。 有些是只在某些條件下存在孽拷,或者根本不存在吨掌。
此時(shí)就可以使用可選屬性,帶有可選屬性的接口與普通的接口定義差不多脓恕,只是在可選屬性名字定義的后面加一個(gè)?符號(hào)膜宋。
interface Square {
color: string
area: number
}
interface SquareConfig {
color?: string
width?: number
}
function createSquare(config: SquareConfig): Square {
let newSquare = { color: 'white', area: 100 }
if (config.color) {
newSquare.color = config.color
}
if (config.width) {
newSquare.area = config.width * config.width
}
return newSquare;
}
let mySquare = createSquare({ color: 'black' })
SquareConfig接口在描述createSquare函數(shù)的參數(shù)約束時(shí),允許參數(shù)對(duì)象含有color和width屬性炼幔,但不是必須的秋茫。
只讀屬性
一些對(duì)象屬性只能在對(duì)象剛剛創(chuàng)建的時(shí)候修改其值。
接口只讀屬性:
interface Point {
readonly x: number
readonly y: number
}
// x,y不能改變
let p1: Point = { x: 10, y: 20 }
// p1.x = 5 // error
泛型只讀數(shù)組:
let a: number[] = [1, 2, 3, 4]
let ro: ReadonlyArray<number> = a
// ro不能修改乃秀,并且
// a = ro // error 泛型只讀數(shù)組不能賦值給普通數(shù)組
// 如需要賦值則使用類(lèi)型斷言
a = ro as number[]
另外const可以聲明只讀變量肛著。也就是說(shuō)如果涉及只讀屬性就用readonly,涉及變量就用const跺讯。
額外的屬性檢查
直接看示例:
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
// ...
return
}
let mySquare = createSquare({ colooour: "red", width: 100 });
如上例所示枢贿,createSquare的調(diào)用會(huì)報(bào)錯(cuò)。你可能會(huì)認(rèn)為抬吟,參數(shù)對(duì)象包含wdith屬性萨咕,類(lèi)型也正確,沒(méi)有color屬性火本,并且colooour屬性可以視作額外無(wú)意義的屬性危队。
盡管如此,TS仍然會(huì)認(rèn)為這樣是有BUG的钙畔。對(duì)象字面量會(huì)被特殊對(duì)待而且會(huì)經(jīng)過(guò)“額外屬性檢查”茫陆,當(dāng)將它們賦值給變量或作為參數(shù)傳遞的時(shí)候。如果一個(gè)對(duì)象字面量存在任何“目標(biāo)類(lèi)型”不包含的屬性時(shí)擎析,你會(huì)得到一個(gè)錯(cuò)誤簿盅。
這句話(huà)應(yīng)該很好理解挥下,在上面示例中,調(diào)用函數(shù)createSquare傳入一個(gè)通過(guò)對(duì)象字面量創(chuàng)建的對(duì)象時(shí)桨醋,這時(shí)經(jīng)過(guò)額外的屬性檢查棚瘟,發(fā)現(xiàn)colooour屬性不是目標(biāo)類(lèi)型包含的屬性,報(bào)出錯(cuò)誤喜最。
解決方式1:使用類(lèi)型斷言
let mySquare = createSquare({ colooour: "red", width: 100 } as SquareConfig);
解決方式2:賦值另外變量
let opt = { colooour: "red", width: 100 };
let mySquare = createSquare(opt);
不同于字面量方式偎蘸,傳入opt對(duì)象不會(huì)經(jīng)過(guò)額外的屬性檢查。
以上兩種方式都可以繞開(kāi)檢查瞬内,更好的實(shí)現(xiàn)方式是
為接口SquareConfig添加字符串簽名索引:
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
這樣做的前提是迷雪,你要確保這個(gè)對(duì)象可以具有一些額外的屬性,如此一來(lái)就允許有任意數(shù)量的屬性虫蝶,并且只要不是color和width章咧,那么就無(wú)所謂是什么類(lèi)型。
在處理復(fù)雜的對(duì)象結(jié)構(gòu)時(shí)可以使用上面的技巧繞開(kāi)檢查能真,但是在簡(jiǎn)單的代碼中盡量不要使用赁严,而是要去修改接口定義來(lái)支持額外的屬性傳入。
函數(shù)類(lèi)型
接口能夠描述JavaScript中對(duì)象擁有的各種各樣的外形舟陆。 除了描述帶有屬性的普通對(duì)象外误澳,接口也可以描述函數(shù)類(lèi)型。
為了使用接口表示函數(shù)類(lèi)型秦躯,我們需要給接口定義一個(gè)調(diào)用簽名。
調(diào)用簽名好比是一個(gè)只有參數(shù)列表和返回值類(lèi)型的函數(shù)定義裆装。
interface SearchFunc {
// 調(diào)用簽名
(source: string, subString: string): boolean
}
這個(gè)接口就是一個(gè)函數(shù)類(lèi)型的接口踱承。
下面來(lái)使用這個(gè)函數(shù)類(lèi)型的接口。
let mySearch: SearchFunc = function (src: string, sub: string): boolean {
let result = src.search(sub)
return result > -1
}
函數(shù)的參數(shù)名不需要與接口里定義的名字相匹配哨免。但是要求對(duì)應(yīng)位置上的參數(shù)類(lèi)型是兼容的茎活,并且返回值類(lèi)型要與接口定義的一致。
可索引的類(lèi)型
與使用接口描述函數(shù)類(lèi)型差不多琢唾,我們也可以描述那些能夠“通過(guò)索引得到”的類(lèi)型载荔,比如a[10]或ageMap["daniel"]。
可索引類(lèi)型具有一個(gè)索引簽名采桃,它描述了對(duì)象索引的類(lèi)型懒熙,還有相應(yīng)的索引返回值類(lèi)型。
索引簽名分為兩種普办,一種是數(shù)字索引簽名工扎,一種是字符串索引簽名。
數(shù)字索引簽名就是在定義一個(gè)具有數(shù)字索引簽名的接口衔蹲,通過(guò)number類(lèi)型去索引得到一個(gè)指定類(lèi)型的返回值肢娘。
interface StringArray {
// 數(shù)字簽名
[index: number]: string
}
let myArray: StringArray = ['bob', 'fred', 'smith'];
let myStr: string = myArray[0];
字符串索引簽名與數(shù)字索引簽名只是索引類(lèi)型不同,
interface StringArray {
// 字符串索引簽名
[index: string]: string
}
let myArray: StringArray = { 'a': 'aaa', 'b': 'bbb' };
let myStr22: string = myArray22['a'];
console.log(myStr22) // aaa
這兩種就是TS支持的兩種索引格式,需要注意的是數(shù)字索引返回值類(lèi)型必須是字符串索引返回值類(lèi)型的子類(lèi)型橱健,因?yàn)樵L(fǎng)問(wèn)數(shù)字索引的時(shí)候而钞,會(huì)將數(shù)字轉(zhuǎn)換為字符串。
如:
let myStr: string = myArray[0];
// 相當(dāng)于
let myStr: string = myArray['0'];
又如:
class Animal {
name: string
}
class Dog extends Animal {
breed: string
}
interface NotOkay {
[x: number]: Dog // 數(shù)字簽名
[x: string]: Animal // 字符串簽名
// 數(shù)字簽名返回的類(lèi)型要是字符串簽名索引返回的子類(lèi)型
}
let an1 = new Animal()
let do1 = new Dog()
let oo1: NotOkay = { 'bb': an1, 2: do1 }
console.log(oo1[2]) // Dog {} 訪(fǎng)問(wèn)數(shù)字簽名時(shí)拘荡,會(huì)將數(shù)字轉(zhuǎn)換為字符串臼节,
// 也就是為什么數(shù)字簽名的返回類(lèi)型要是字符串簽名返回類(lèi)型的子類(lèi)型
console.log(oo1['bb']) // Animal {}
此外,字符串索引簽名能夠很好的描述dictionary模式俱病,并且它們也會(huì)確保所有屬性與其返回值類(lèi)型相匹配官疲。
如:
interface NumberDictionary {
[index: string]: number;
length: number; // 可以,length是number類(lèi)型
name: string // 錯(cuò)誤亮隙,`name`的類(lèi)型與索引類(lèi)型返回值的類(lèi)型不匹配
}
最后你還可以給索引簽名設(shè)置為只讀途凫,防止了給索引賦值:
interface ReadonlyStringArray {
readonly [index: number]: string;
}
類(lèi)類(lèi)型
TS中類(lèi)也可以去實(shí)現(xiàn)接口,讓類(lèi)具有某種約束或者契約溢吻。
interface ClockInterface {
currentTime: Date
setTime(d: Date)
}
// 類(lèi)實(shí)現(xiàn)接口就要擁有接口中定義的屬性和方法
class Clock2 implements ClockInterface {
currentTime: Date
constructor(h: number, m: number) {
}
setTime(d: Date) {
this.currentTime = d
}
}
類(lèi)是具有兩個(gè)類(lèi)型的:靜態(tài)部分的類(lèi)型和實(shí)例的類(lèi)型维费。實(shí)現(xiàn)接口的叫實(shí)例類(lèi)型。
類(lèi)實(shí)現(xiàn)接口促王,接口只描述了類(lèi)的公共部分(也就是類(lèi)的實(shí)例類(lèi)型)犀盟,是不會(huì)檢查類(lèi)的私有成員。
實(shí)現(xiàn)接口的屬性和方法屬于類(lèi)的實(shí)例類(lèi)型蝇狼。
類(lèi)的構(gòu)造器是靜態(tài)類(lèi)型阅畴。
構(gòu)造器接口
用構(gòu)造器簽名去定義一個(gè)接口并試圖定義一個(gè)類(lèi)去實(shí)現(xiàn)這個(gè)接口時(shí)會(huì)得到一個(gè)錯(cuò)誤,因?yàn)樯厦嬲f(shuō)過(guò)迅耘,構(gòu)造器存在于類(lèi)的靜態(tài)部分贱枣,類(lèi)只會(huì)對(duì)實(shí)例部分做類(lèi)型檢查。
下面的做法是錯(cuò)誤的:
interface ClockConstructor {
// 構(gòu)造器簽名
new(hour: number, minute: number)
}
class Clock implements ClockConstructor {
currentTime: Date;
constructor(h: number, m: number) { }
}
如何使用構(gòu)造器接口
我們學(xué)過(guò)了實(shí)例接口颤专,構(gòu)造器接口纽哥。又知道一個(gè)類(lèi)不能直接實(shí)現(xiàn)一個(gè)構(gòu)造器接口。那如何使用構(gòu)造器接口呢栖秕?看下面的示例:
// 為實(shí)例方法所用
interface ClockInterface {
tick()
}.
// 為構(gòu)造函數(shù)所用
interface ClockConstructor {
// 構(gòu)造函數(shù)簽名
new(hour: number, minute: number): ClockInterface
}
// 會(huì)檢查第一個(gè)參數(shù)是否符合構(gòu)造函數(shù)簽名ClockConstructor
function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
return new ctor(hour, minute)
}
// 定義兩個(gè)類(lèi)實(shí)現(xiàn)實(shí)例接口
class DigitalClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log('tick Digital');
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log('tick Analog');
}
}
let digital = createClock(DigitalClock, 12, 17)
let anglog = createClock(AnalogClock, 7, 32)
因?yàn)?code>createClock函數(shù)的第一個(gè)參數(shù)是ClockConstructor
類(lèi)型春塌,在調(diào)用createClock
時(shí),會(huì)檢查DigitalClock簇捍、AnalogClock
是否符合構(gòu)造函數(shù)簽名只壳。又因?yàn)?code>DigitalClock、AnalogClock類(lèi)都實(shí)現(xiàn)了接口ClockInterface
垦写,并且ClockConstructor
構(gòu)造器接口簽名返回值類(lèi)型為ClockInterface
吕世,那么DigitalClock、AnalogClock
類(lèi)都是滿(mǎn)足條件的梯投。
這樣我們就實(shí)現(xiàn)了一個(gè)工廠方法命辖,用傳進(jìn)的類(lèi)型創(chuàng)建實(shí)例况毅,并且該類(lèi)型受構(gòu)造器接口約束。
繼承接口
接口也可以相互繼承 從一個(gè)接口里復(fù)制成員到另一個(gè)接口尔艇,更靈活的將接口分割到一些可重用的模塊尔许。并且可以同時(shí)繼承多個(gè)接口。
interface Shape {
color: string
}
interface PenStroke {
penWidth: number
}
interface Square extends Shape, PenStroke {
sideLength: number
}
// 一個(gè)接口可以繼承多個(gè)接口终娃,創(chuàng)建出多個(gè)接口的合成接口
let squre = {} as Square
squre.color = 'blue'
squre.sideLength = 10
squre.penWidth = 5.0
混合類(lèi)型
先前我們提過(guò)味廊,接口能夠描述JavaScript里豐富的類(lèi)型。 因?yàn)镴avaScript其動(dòng)態(tài)靈活的特點(diǎn)棠耕,有時(shí)你會(huì)希望一個(gè)對(duì)象可以同時(shí)具有上面提到的多種類(lèi)型余佛。
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) { };
counter.interval = 123;
counter.reset = function () { };
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
例子中g(shù)etCounter返回的對(duì)象是Counter類(lèi)型,該對(duì)象即是一個(gè)函數(shù)類(lèi)型窍荧,同時(shí)又是一個(gè)對(duì)象類(lèi)型辉巡,有reset方法和interval屬性。
*接口繼承類(lèi)
接口繼承類(lèi)蕊退,使用場(chǎng)景不多郊楣。
接口繼承類(lèi)時(shí),會(huì)繼承類(lèi)的成員瓤荔,但不包括實(shí)現(xiàn)净蚤。好像接口聲明了所有類(lèi)存在的成員,但沒(méi)有提供具體實(shí)現(xiàn)输硝,同時(shí)也會(huì)也會(huì)繼承私有和受保護(hù)的成今瀑。
這也就意味著,一個(gè)接口繼承了一個(gè)擁有私有或者受保護(hù)成員的類(lèi)時(shí)点把,那么這個(gè)接口只能夠被這個(gè)類(lèi)或其子類(lèi)實(shí)現(xiàn)放椰。
看一個(gè)示例:
class Control {
private state: any
}
interface SelectableControl extends Control {
select()
}
// 聲明Button類(lèi)并繼承Control類(lèi),然后實(shí)現(xiàn)SelectableControl接口
class Button extends Control implements SelectableControl {
select() { }
}
// 這個(gè)類(lèi)沒(méi)有繼承Control就實(shí)現(xiàn)了接口愉粤,是不行的,缺少私有成員state
// class ImageC implements SelectableControl {
// select() { }
// }
接口SelectableControl繼承了Control類(lèi)拿撩,那么就是說(shuō)接口也繼承了類(lèi)中的成員state衣厘。那么只有Control類(lèi)的子類(lèi)才能實(shí)現(xiàn)這個(gè)接口。
上面代碼中Button類(lèi)繼承了Control類(lèi)压恒,那么也就自然的繼承了類(lèi)中的成員state影暴,然后又實(shí)現(xiàn)了SelectableControl接口,接口要求實(shí)現(xiàn)它的類(lèi)中有state成員探赫,Button滿(mǎn)足型宙,所以這樣是正確的。
ImageC類(lèi)沒(méi)有繼承Control類(lèi)就去實(shí)現(xiàn)SelectableControl接口伦吠,接口檢查到類(lèi)中沒(méi)有成員state妆兑,所以報(bào)錯(cuò)魂拦。
完
接口相關(guān)的內(nèi)容就是這么多,接口在TS扮演了非常重要的角色搁嗓,使用廣泛芯勘。