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

TypeScript定義接口
熟悉編程語言的同窗都知道,接口(interface)的重要性不言而喻。 不少內容都會運用到接口。typescrip中的接口相似於java,同時還增長了更靈活的接口類型,包括屬性、函數、可索引和類等,要想對typescript的操做進行更深刻的瞭解,接口是必須接觸到的。今天我就爲你們分享一下,如何使用接口。java

一. 爲何要使用接口
1.1. JavaScript存在的問題
咱們在JavaScript中定義一個函數,用於獲取一個用戶的姓名和年齡的字符串:
const getUserInfo = function(user) {
return name: ${user.name}, age: ${user.age}
}
正確的調用方法應該是下面的方式:
getUserInfo({name: "coderwhy", age: 18})
可是當項目比較大,或者多人開發時,會出現錯誤的調用方法:
// 錯誤的調用
getUserInfo() // Uncaught TypeError: Cannot read property 'name' of undefined
console.log(getUserInfo({name: "coderwhy"})) // name: coderwhy, age: undefined
getUserInfo({name: "codewhy", height: 1.88}) // name: coderwhy, age: undefined
由於JavaScript是弱類型的語言,因此並不會對咱們傳入的代碼進行任何的檢測,可是在以前的javaScript中確確實實會存在不少相似的安全隱患。
如何避免這樣的問題呢?
固然是使用TypeScript來對代碼進行重構
1.2. TypeScript代碼重構一
咱們可使用TypeScript來對上面的代碼進行改進:
const getUserInfo = (user: {name: string, age: number}): string => {
return name: ${user.name} age: ${user.age};
};
正確的調用是以下的方式:
getUserInfo({name: "coderwhy", age: 18});
若是調用者出現了錯誤的調用,那麼TypeScript會直接給出錯誤的提示信息:
// 錯誤的調用
getUserInfo(); // 錯誤信息:An argument for 'user' was not provided.
getUserInfo({name: "coderwhy"}); // 錯誤信息:Property 'age' is missing in type '{ name: string; }'
getUserInfo({name: "coderwhy", height: 1.88}); // 錯誤信息:類型不匹配
這樣確實能夠防止出現錯誤的調用,可是咱們在定義函數的時候,參數的類型和函數的類型都是很是長的,代碼很是不便於閱讀。
因此,咱們可使用接口來對代碼再次進行重構。
1.3. TypeScript代碼重構二
如今咱們使用接口來對user的類型進行重構。
接口重構一:參數類型使用接口定義
咱們先定義一個IUser接口:
// 先定義一個接口
interface IUser {
name: string;
age: number;
}
接下來咱們看一下函數如何來寫:
const getUserInfo = (user: IUser): string => {
return name: ${user.name}, age: ${user.age};
};typescript

// 正確的調用
getUserInfo({name: "coderwhy", age: 18});編程

// 錯誤的調用,其餘也是同樣
getUserInfo();
接口重構二:函數的類型使用接口定義好(後面會詳細講解接口函數的定義)
咱們先定義兩個接口:
第二個接口定義有一個警告,咱們暫時忽略它,它的目的是若是一個函數接口只有一個方法,那麼可使用type來定義
type IUserInfoFunc = (user: IUser) => string;
interface IUser {
name: string;
age: number;
}api

interface IUserInfoFunc {
(user: IUser): string;
}
接着咱們去定義函數和調用函數便可:
const getUserInfo: IUserInfoFunc = (user) => {
return name: ${user.name}, age: ${user.age};
};數組

// 正確的調用
getUserInfo({name: "coderwhy", age: 18});安全

// 錯誤的調用
getUserInfo();
二. 接口的基本使用
2.1. 接口的定義方式
和其餘不少的語言相似,TypeScript中定義接口也是使用interface關鍵字來定義:
interface IPerson {
name: string;
}
你會發現我都在接口的前面加了一個I,這是tslint要求的,不然會報一個警告
要不要加前綴是根據公司規範和我的習慣
interface name must start with a capitalized I
固然咱們能夠在tslint中關閉掉它:在rules中添加以下規則
"interface-name" : [true, "never-prefix"]
2.2. 接口中定義方法
定義接口中不只僅能夠有屬性,也能夠有方法:
interface Person {
name: string;
run(): void;
eat(): void;
}
若是咱們有一個對象是該接口類型,那麼必須包含對應的屬性和方法:
const p: Person = {
name: "why",
run() {
console.log("running");
},
eat() {
console.log("eating");
},
};
2.3. 可選屬性的定義
默認狀況下一個變量(對象)是對應的接口類型,那麼這個變量(對象)必須實現接口中全部的屬性和方法。
可是,開發中爲了讓接口更加的靈活,某些屬性咱們可能但願設計成可選的(想實現能夠實現,不想實現也沒有關係),這個時候就可使用可選屬性(後面詳細講解函數時,也會講到函數中有可選參數):
interface Person {
name: string;
age?: number;
run(): void;
eat(): void;
study?(): void;
}
上面的代碼中,咱們增長了age屬性和study方法,這兩個都是可選的:
可選屬性若是沒有賦值,那麼獲取到的值是undefined;
對於可選方法,必須先進行判斷,再調用,不然會報錯;
const p: Person = {
name: "why",
run() {
console.log("running");
},
eat() {
console.log("eating");
},
};編程語言

console.log(p.age); // undefined
p.study(); // 不能調用多是「未定義」的對象。
正確的調用方式以下:
if (p.study) {
p.study();
}
2.4. 只讀屬性的定義
默認狀況下,接口中定義的屬性可讀可寫:
console.log(p.name);
p.name = "流川楓";
若是一個屬性,咱們只是但願在定義的時候就定義值,以後不能夠修改,那麼能夠在屬性的前面加上一個關鍵字:readonly
interface Person {
readonly name: string;
age?: number;
run(): void;
eat(): void;
study?(): void;
}
當我在name前面加上readonly時,賦值語句就會報錯:
console.log(p.name);
p.name = "流川楓"; // Cannot assign to 'name' because it is a read-only property.
三. 接口的高級使用
3.1. 函數類型的定義
接口不只僅能夠定義普通的對象類型,也能夠定義函數的類型
// 函數類型的定義
interface SumFunc {
(num1: number, num2: number): number;
}ide

// 定義具體的函數
const sum: SumFunc = (num1, num2) => {
return num1 + num2;
};函數

// 調用函數
console.log(sum(20, 30));
不過上面的接口中只有一個函數,TypeScript會給咱們一個建議,可使用type來定義一個函數的類型:
type SumFunc = (num1: number, num2: number) => number;
關於type的更多用戶,咱們後面專門進行講解,暫時不在接口中展開討論。
3.2. 可索引類型的定義
和使用接口描述函數的類型差很少,咱們也可使用接口來描述 可索引類型
好比一個變量能夠這樣訪問:a[3],a["name"]
可索引類型具備一個 索引簽名,它描述了對象索引的類型,還有相應的索引返回值類型。
// 定義可索引類型的接口
interface RoleMap {學習

}

// 賦值具體的值
// 賦值方式一:
const roleMap1: RoleMap = {
0: "學生",
1: "講師",
2: "班主任",
};

// 賦值方式二:由於數組自己是可索引的值
const roleMap2 = ["魯班七號", "露娜", "李白"];

// 取出對應的值
console.log(roleMap1[0]); // 學生
console.log(roleMap2[1]); // 露娜
上面的案例中,咱們的索引簽名是數字類型, TypeScript支持兩種索引簽名:字符串和數字。
咱們來定義一個字符串的索引類型:
interface RoleMap {

}

const roleMap: RoleMap = {
aaa: "魯班七號",
bbb: "露娜",
ccc: "李白",
};

console.log(roleMap.aaa);
console.log(roleMap["aaa"]); // 警告:不推薦這樣來取
能夠同時使用兩種類型的索引,可是數字索引的返回值必須是字符串索引返回值類型的子類型:
這是由於當使用 number來索引時,JavaScript會將它轉換成string而後再去索引對象。
class Person {
private name: string = "";
}

class Student extends Person {
private sno: number = 0;
}

// 下面的代碼會報錯
interface IndexSubject {

}
代碼會報以下錯誤:
數字索引類型「Person」不能賦給字符串索引類型「Student」。
修改成以下代碼就能夠了:
interface IndexSubject {

}
下面的代碼也會報錯:
letter索引獲得結果的類型,必須是Person類型或者它的子類型
interface IndexSubject {

letter: string;
}
3.3. 接口的實現

注意:在這個小節以及下一個小節中,咱們會寫一些類,可是目前尚未詳細學習類的語法(雖然TS的類和ES6的很是類似)。
你們能夠先知道咱們的類如何定義,如何去和接口配合使用的便可,一些細節我會有專門的文章來解決類的使用。

接口除了定義某種類型規範以後,也能夠和其餘編程語言同樣,讓一個類去實現某個接口,那麼這個類就必須明確去擁有這個接口中的屬性和實現其方法:
下面的代碼中會有關於修飾符的警告,暫時忽略,後面詳細講解
// 定義一個實體接口
interface Entity {
title: string;
log(): void;
}

// 實現這樣一個接口
class Post implements Entity {
title: string;

constructor(title: string) {
this.title = title;
}

log(): void {
console.log(this.title);
}
}
思考:我定義了一個接口,可是我在繼承這個接口的類中還要寫接口的實現方法,那我不如直接就在這個類中寫實現方法豈不是更便捷,還省去了定義接口?這是一個初學者常常會有疑惑的地方。
從思考方式上,爲何須要接口?

咱們從生活出發理解接口


好比你去三亞/杭州旅遊, 玩了一上午後飢餓難耐, 你放眼望去, 會注意什麼? 飯店!!


你可能並不會太在乎這家飯店叫什麼名字, 可是你知道只要後面有飯店兩個字, 就意味着這個地方必然有飯店的實現 – 作各類菜給你吃;


接口就比如飯店/酒店這些名詞後面添加的附屬詞, 當咱們看到這些附屬詞後就知道它們具有的功能

從代碼設計上,爲何須要接口?
在代碼設計中,接口是一種規範;
接口一般用於來定義某種規範, 相似於你必須遵照的協議, 有些語言直接就叫protocol;
站在程序角度上說接口只規定了類裏必須提供的屬性和方法,從而分離了規範和實現,加強了系統的可拓展性和可維護性;
固然,對於初次接觸接口的人,仍是很難理解它在實際的代碼設計中的好處,這點慢慢體會,不用心急。
3.3. 接口的繼承
和類類似(後面咱們再詳細學習類的知識),接口也是能夠繼承接口來提供複用性:
注意:繼承使用extends關鍵字
interface Barkable {
barking(): void;
}

interface Shakable {
shaking(): void;
}

interface Petable extends Barkable, Shakable {
eating(): void;
}
接口Petable繼承自Barkable和Shakable,另外咱們發現一個接口能夠同時繼承自多個接口
若是如今有一個類實現了Petable接口,那麼不只僅須要實現Petable的方法,也須要實現Petable繼承自的接口中的方法:
注意:實現接口使用implements關鍵字
class Dog implements Petable {
barking(): void {
console.log("汪汪叫");
}

shaking(): void {
console.log("搖尾巴");
}

eating(): void {
console.log("吃骨頭");
}
}

若是你以爲接口的內容就僅僅侷限於此,那可就大錯特錯了,接口也要結合其餘的知識同時運用,這其中必然少不了你反覆的練習,若是你想提高你的編程能力,那就關注我,我會爲你發佈更多的精彩教程,幫助你突破瓶頸,提高自我。

相關文章
相關標籤/搜索