typescript學(xué)習(xí)筆記——(二)接口

你要知道的

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扮演了非常重要的角色搁嗓,使用廣泛芯勘。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市腺逛,隨后出現(xiàn)的幾起案子荷愕,更是在濱河造成了極大的恐慌,老刑警劉巖棍矛,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件安疗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡够委,警方通過(guò)查閱死者的電腦和手機(jī)荐类,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)慨绳,“玉大人掉冶,你說(shuō)我怎么就攤上這事∑暄” “怎么了厌小?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)战秋。 經(jīng)常有香客問(wèn)我璧亚,道長(zhǎng),這世上最難降的妖魔是什么脂信? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任癣蟋,我火速辦了婚禮,結(jié)果婚禮上狰闪,老公的妹妹穿的比我還像新娘疯搅。我一直安慰自己,他們只是感情好埋泵,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布幔欧。 她就那樣靜靜地躺著,像睡著了一般丽声。 火紅的嫁衣襯著肌膚如雪礁蔗。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,692評(píng)論 1 305
  • 那天雁社,我揣著相機(jī)與錄音浴井,去河邊找鬼。 笑死霉撵,一個(gè)胖子當(dāng)著我的面吹牛磺浙,可吹牛的內(nèi)容都是我干的洪囤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼屠缭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼箍鼓!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起呵曹,我...
    開(kāi)封第一講書(shū)人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤款咖,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后奄喂,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體铐殃,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年跨新,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了富腊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡域帐,死狀恐怖赘被,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情肖揣,我是刑警寧澤民假,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站龙优,受9級(jí)特大地震影響羊异,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜彤断,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一野舶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宰衙,春花似錦平道、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至劲蜻,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間考余,已是汗流浹背先嬉。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留楚堤,地道東北人疫蔓。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓含懊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親衅胀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子岔乔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容