探索typescript的必經(jīng)之路-----接口(interface)

TypeScript定義接口

熟悉編程語(yǔ)言的同學(xué)都知道,接口(interface)的重要性不言而喻。?很多內(nèi)容都會(huì)運(yùn)用到接口。typescrip中的接口類似于java,同時(shí)還增加了更靈活的接口類型,包括屬性、函數(shù)、可索引和類等,要想對(duì)typescript的操作進(jìn)行更深入的了解,接口是必須接觸到的。今天我就為大家分享一下,如何使用接口。

一. 為什么要使用接口

1.1. JavaScript存在的問(wèn)題

我們?cè)贘avaScript中定義一個(gè)函數(shù),用于獲取一個(gè)用戶的姓名和年齡的字符串:

const?getUserInfo = function(user) {??return?`name: ${user.name}, age: ${user.age}`}

正確的調(diào)用方法應(yīng)該是下面的方式:

getUserInfo({name: "coderwhy", age: 18})

但是當(dāng)項(xiàng)目比較大,或者多人開發(fā)時(shí),會(huì)出現(xiàn)錯(cuò)誤的調(diào)用方法:

// 錯(cuò)誤的調(diào)用getUserInfo() // Uncaught TypeError: Cannot read property 'name' of undefinedconsole.log(getUserInfo({name: "coderwhy"})) // name: coderwhy, age: undefinedgetUserInfo({name: "codewhy", height: 1.88}) // name: coderwhy, age: undefined

因?yàn)镴avaScript是弱類型的語(yǔ)言,所以并不會(huì)對(duì)我們傳入的代碼進(jìn)行任何的檢測(cè),但是在之前的javaScript中確確實(shí)實(shí)會(huì)存在很多類似的安全隱患。

如何避免這樣的問(wèn)題呢?

[if !supportLists]·?[endif]當(dāng)然是使用TypeScript來(lái)對(duì)代碼進(jìn)行重構(gòu)

1.2. TypeScript代碼重構(gòu)一

我們可以使用TypeScript來(lái)對(duì)上面的代碼進(jìn)行改進(jìn):

const?getUserInfo = (user: {name: string, age: number}): string =>?{??return?`name: ${user.name}?age: ${user.age}`;};

正確的調(diào)用是如下的方式:

getUserInfo({name: "coderwhy", age: 18});

如果調(diào)用者出現(xiàn)了錯(cuò)誤的調(diào)用,那么TypeScript會(huì)直接給出錯(cuò)誤的提示信息:

// 錯(cuò)誤的調(diào)用getUserInfo(); // 錯(cuò)誤信息:An argument for 'user' was not provided.getUserInfo({name: "coderwhy"}); // 錯(cuò)誤信息:Property 'age' is missing in type '{ name: string; }'getUserInfo({name: "coderwhy", height: 1.88}); // 錯(cuò)誤信息:類型不匹配

這樣確實(shí)可以防止出現(xiàn)錯(cuò)誤的調(diào)用,但是我們?cè)诙x函數(shù)的時(shí)候,參數(shù)的類型和函數(shù)的類型都是非常長(zhǎng)的,代碼非常不便于閱讀。

所以,我們可以使用接口來(lái)對(duì)代碼再次進(jìn)行重構(gòu)。

1.3. TypeScript代碼重構(gòu)二

現(xiàn)在我們使用接口來(lái)對(duì)user的類型進(jìn)行重構(gòu)。

接口重構(gòu)一:參數(shù)類型使用接口定義

我們先定義一個(gè)IUser接口:

// 先定義一個(gè)接口interface?IUser {??name: string;??age: number;}

接下來(lái)我們看一下函數(shù)如何來(lái)寫:

const?getUserInfo = (user: IUser): string =>?{??return?`name: ${user.name}, age: ${user.age}`;};// 正確的調(diào)用getUserInfo({name: "coderwhy", age: 18});// 錯(cuò)誤的調(diào)用,其他也是一樣getUserInfo();

接口重構(gòu)二:函數(shù)的類型使用接口定義好(后面會(huì)詳細(xì)講解接口函數(shù)的定義)

我們先定義兩個(gè)接口:

[if !supportLists]·?[endif]第二個(gè)接口定義有一個(gè)警告,我們暫時(shí)忽略它,它的目的是如果一個(gè)函數(shù)接口只有一個(gè)方法,那么可以使用type來(lái)定義

[if !supportLists]·?[endif]type IUserInfoFunc = (user: IUser) => string;

interface?IUser {??name: string;??age: number;}interface?IUserInfoFunc {??(user: IUser): string;}

接著我們?nèi)ザx函數(shù)和調(diào)用函數(shù)即可:

const?getUserInfo: IUserInfoFunc = (user) =>?{??return?`name: ${user.name}, age: ${user.age}`;};// 正確的調(diào)用getUserInfo({name: "coderwhy", age: 18});// 錯(cuò)誤的調(diào)用getUserInfo();

二. 接口的基本使用

2.1. 接口的定義方式

和其他很多的語(yǔ)言類似,TypeScript中定義接口也是使用interface關(guān)鍵字來(lái)定義:

interface?IPerson {??name: string;}

你會(huì)發(fā)現(xiàn)我都在接口的前面加了一個(gè)I,這是tslint要求的,否則會(huì)報(bào)一個(gè)警告

[if !supportLists]·?[endif]要不要加前綴是根據(jù)公司規(guī)范和個(gè)人習(xí)慣

interface name must start with a capitalized I

當(dāng)然我們可以在tslint中關(guān)閉掉它:在rules中添加如下規(guī)則

"interface-name"?: [true, "never-prefix"]

2.2. 接口中定義方法

定義接口中不僅僅可以有屬性,也可以有方法:

interface?Person {??name: string;??run(): void;??eat(): void;}

如果我們有一個(gè)對(duì)象是該接口類型,那么必須包含對(duì)應(yīng)的屬性和方法:

const?p: Person = {??name: "why",??run() {????console.log("running");??},??eat() {????console.log("eating");??},};

2.3. 可選屬性的定義

默認(rèn)情況下一個(gè)變量(對(duì)象)是對(duì)應(yīng)的接口類型,那么這個(gè)變量(對(duì)象)必須實(shí)現(xiàn)接口中所有的屬性和方法。

但是,開發(fā)中為了讓接口更加的靈活,某些屬性我們可能希望設(shè)計(jì)成可選的(想實(shí)現(xiàn)可以實(shí)現(xiàn),不想實(shí)現(xiàn)也沒(méi)有關(guān)系),這個(gè)時(shí)候就可以使用可選屬性(后面詳細(xì)講解函數(shù)時(shí),也會(huì)講到函數(shù)中有可選參數(shù)):

interface?Person {??name: string;??age?: number;??run(): void;??eat(): void;??study?(): void;}

上面的代碼中,我們?cè)黾恿薬ge屬性和study方法,這兩個(gè)都是可選的:

[if !supportLists]·?[endif]可選屬性如果沒(méi)有賦值,那么獲取到的值是undefined;

[if !supportLists]·?[endif]對(duì)于可選方法,必須先進(jìn)行判斷,再調(diào)用,否則會(huì)報(bào)錯(cuò);

const?p: Person = {??name: "why",??run() {????console.log("running");??},??eat() {????console.log("eating");??},};console.log(p.age); // undefinedp.study(); // 不能調(diào)用可能是“未定義”的對(duì)象。

正確的調(diào)用方式如下:

if?(p.study) {??p.study();}

2.4. 只讀屬性的定義

默認(rèn)情況下,接口中定義的屬性可讀可寫:

console.log(p.name);p.name = "流川楓";

如果一個(gè)屬性,我們只是希望在定義的時(shí)候就定義值,之后不可以修改,那么可以在屬性的前面加上一個(gè)關(guān)鍵字:readonly

interface?Person {??readonly name: string;??age?: number;??run(): void;??eat(): void;??study?(): void;}

當(dāng)我在name前面加上readonly時(shí),賦值語(yǔ)句就會(huì)報(bào)錯(cuò):

console.log(p.name);p.name = "流川楓"; // Cannot assign to 'name' because it is a read-only property.

三. 接口的高級(jí)使用

3.1. 函數(shù)類型的定義

接口不僅僅可以定義普通的對(duì)象類型,也可以定義函數(shù)的類型

// 函數(shù)類型的定義interface?SumFunc {??(num1: number, num2: number): number;}// 定義具體的函數(shù)const?sum: SumFunc = (num1, num2) =>?{??return?num1 + num2;};// 調(diào)用函數(shù)console.log(sum(20, 30));

不過(guò)上面的接口中只有一個(gè)函數(shù),TypeScript會(huì)給我們一個(gè)建議,可以使用type來(lái)定義一個(gè)函數(shù)的類型:

type?SumFunc = (num1: number, num2: number) =>?number;

關(guān)于type的更多用戶,我們后面專門進(jìn)行講解,暫時(shí)不在接口中展開討論。

3.2. 可索引類型的定義

和使用接口描述函數(shù)的類型差不多,我們也可以使用接口來(lái)描述可索引類型

[if !supportLists]·?[endif]比如一個(gè)變量可以這樣訪問(wèn):a[3],a["name"]

可索引類型具有一個(gè)索引簽名,它描述了對(duì)象索引的類型,還有相應(yīng)的索引返回值類型。

// 定義可索引類型的接口interface?RoleMap {??[index: number]: string;}// 賦值具體的值// 賦值方式一:const?roleMap1: RoleMap = {??0: "學(xué)生",??1: "講師",??2: "班主任",};// 賦值方式二:因?yàn)閿?shù)組本身是可索引的值const?roleMap2 = ["魯班七號(hào)", "露娜", "李白"];// 取出對(duì)應(yīng)的值console.log(roleMap1[0]); // 學(xué)生console.log(roleMap2[1]); // 露娜

上面的案例中,我們的索引簽名是數(shù)字類型,TypeScript支持兩種索引簽名:字符串和數(shù)字。

我們來(lái)定義一個(gè)字符串的索引類型:

interface?RoleMap {??[name: string]: string;}const?roleMap: RoleMap = {??aaa: "魯班七號(hào)",??bbb: "露娜",??ccc: "李白",};console.log(roleMap.aaa);console.log(roleMap["aaa"]); // 警告:不推薦這樣來(lái)取

可以同時(shí)使用兩種類型的索引,但是數(shù)字索引的返回值必須是字符串索引返回值類型的子類型:

[if !supportLists]·?[endif]這是因?yàn)楫?dāng)使用number來(lái)索引時(shí),JavaScript會(huì)將它轉(zhuǎn)換成string然后再去索引對(duì)象。

class?Person {??private?name: string?= "";}class?Student extends?Person {??private?sno: number?= 0;}// 下面的代碼會(huì)報(bào)錯(cuò)interface?IndexSubject {??[index: number]: Person;??[name: string]: Student;}

代碼會(huì)報(bào)如下錯(cuò)誤:

數(shù)字索引類型“Person”不能賦給字符串索引類型“Student”。

修改為如下代碼就可以了:

interface?IndexSubject {??[index: number]: Student;??[name: string]: Person;}

下面的代碼也會(huì)報(bào)錯(cuò):

[if !supportLists]·?[endif]letter索引得到結(jié)果的類型,必須是Person類型或者它的子類型

interface?IndexSubject {??[index: number]: Student;??[name: string]: Person;??letter: string;}

3.3. 接口的實(shí)現(xiàn)

?

注意:在這個(gè)小節(jié)以及下一個(gè)小節(jié)中,我們會(huì)寫一些類,但是目前還沒(méi)有詳細(xì)學(xué)習(xí)類的語(yǔ)法(雖然TS的類和ES6的非常相似)。

大家可以先知道我們的類如何定義,如何去和接口配合使用的即可,一些細(xì)節(jié)我會(huì)有專門的文章來(lái)解決類的使用。

接口除了定義某種類型規(guī)范之后,也可以和其他編程語(yǔ)言一樣,讓一個(gè)類去實(shí)現(xiàn)某個(gè)接口,那么這個(gè)類就必須明確去擁有這個(gè)接口中的屬性和實(shí)現(xiàn)其方法:

[if !supportLists]·?[endif]下面的代碼中會(huì)有關(guān)于修飾符的警告,暫時(shí)忽略,后面詳細(xì)講解

// 定義一個(gè)實(shí)體接口interface?Entity {??title: string;??log(): void;}// 實(shí)現(xiàn)這樣一個(gè)接口class?Post implements?Entity {??title: string;??constructor(title: string) {????this.title = title;??}??log(): void?{????console.log(this.title);??}}

思考:我定義了一個(gè)接口,但是我在繼承這個(gè)接口的類中還要寫接口的實(shí)現(xiàn)方法,那我不如直接就在這個(gè)類中寫實(shí)現(xiàn)方法豈不是更便捷,還省去了定義接口?這是一個(gè)初學(xué)者經(jīng)常會(huì)有疑惑的地方。

從思考方式上,為什么需要接口?

[if !supportLists]·?[endif]

我們從生活出發(fā)理解接口

[if !supportLists]·?[endif]

[if !supportLists]·?[endif]

比如你去三亞/杭州旅游, 玩了一上午后饑餓難耐, 你放眼望去, 會(huì)注意什么? 飯店!!

[if !supportLists]·?[endif]

[if !supportLists]·?[endif]

你可能并不會(huì)太在意這家飯店叫什么名字, 但是你知道只要后面有飯店兩個(gè)字, 就意味著這個(gè)地方必然有飯店的實(shí)現(xiàn) – 做各種菜給你吃;

[if !supportLists]·?[endif]

[if !supportLists]·?[endif]

接口就好比飯店/酒店/棋牌室這些名詞后面添加的附屬詞, 當(dāng)我們看到這些附屬詞后就知道它們具備的功能

[if !supportLists]·?[endif]

從代碼設(shè)計(jì)上,為什么需要接口?

[if !supportLists]·?[endif]在代碼設(shè)計(jì)中,接口是一種規(guī)范;

[if !supportLists]·?[endif]接口通常用于來(lái)定義某種規(guī)范, 類似于你必須遵守的協(xié)議, 有些語(yǔ)言直接就叫protocol;

[if !supportLists]·?[endif]站在程序角度上說(shuō)接口只規(guī)定了類里必須提供的屬性和方法,從而分離了規(guī)范和實(shí)現(xiàn),增強(qiáng)了系統(tǒng)的可拓展性和可維護(hù)性;

當(dāng)然,對(duì)于初次接觸接口的人,還是很難理解它在實(shí)際的代碼設(shè)計(jì)中的好處,這點(diǎn)慢慢體會(huì),不用心急。

3.3. 接口的繼承

和類相似(后面我們?cè)僭敿?xì)學(xué)習(xí)類的知識(shí)),接口也是可以繼承接口來(lái)提供復(fù)用性:

[if !supportLists]·?[endif]注意:繼承使用extends關(guān)鍵字

interface?Barkable {??barking(): void;}interface?Shakable {??shaking(): void;}interface?Petable extends?Barkable, Shakable {??eating(): void;}

接口Petable繼承自Barkable和Shakable,另外我們發(fā)現(xiàn)一個(gè)接口可以同時(shí)繼承自多個(gè)接口

如果現(xiàn)在有一個(gè)類實(shí)現(xiàn)了Petable接口,那么不僅僅需要實(shí)現(xiàn)Petable的方法,也需要實(shí)現(xiàn)Petable繼承自的接口中的方法:

[if !supportLists]·?[endif]注意:實(shí)現(xiàn)接口使用implements關(guān)鍵字

class?Dog implements?Petable {??barking(): void?{????console.log("汪汪叫");??}??shaking(): void?{????console.log("搖尾巴");??}??eating(): void?{????console.log("吃骨頭");??}}

如果你覺得接口的內(nèi)容就僅僅局限于此,那可就大錯(cuò)特錯(cuò)了,接口也要結(jié)合其他的知識(shí)同時(shí)運(yùn)用,這其中必然少不了你反復(fù)的練習(xí),如果你想提升你的編程能力,那就關(guān)注我,我會(huì)為你發(fā)布更多的精彩教程,幫助你突破瓶頸,提升自我。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容