接口帶來了什么好處
好處One —— 過去我們寫 JavaScript
JavaScript 中定義一個函數,用來獲取一個用戶的姓名和年齡的字符串:
const getUserInfo = function(user) { return name: ${user.name}, age: ${user.age} }
函數調用:
getUserInfo({name: "koala", age: 18})
這對於我們之前在寫 JavaScript 的時候,再正常不過了,但是如果這個 getUserInfo 在多人開發過程中,如果它是個公共函數,多個開發者都會調用,如果不是每個人點進來看函數對應注釋,可能會出現以下問題:
// 錯誤的調用 getUserInfo() // Uncaught TypeError: Cannot read property 'name' of undefined console.log(getUserInfo({name: "kaola"})) // name: kaola, age: undefined getUserInfo({name: "kaola", height: 1.66}) // name: koala, age: undefined
JavaScript 是弱類型的語言,所以並不會對我們傳入的代碼進行任何的檢測,有些錯你自己都說不清楚,但是就出了問題。
TypeScript 中的 interface 可以解決這個問題
const getUserInfo = (user: {name: string, age: number}): string => {
return `name: ${user.name} age: ${user.age}`;
};
正確的調用是如下的方式:
getUserInfo({name: "kaola", 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}); // 錯誤信息:類型不匹配
這時候你會發現這段代碼還是有點長,代碼不便與閱讀,這時候就體現了 interface 的必要性。
使用 interface 對 user 的類型進行重構。
我們先定義一個 IUser 接口:
// 先定義一個接口 interface IUser { name: string; age: number; }
接下來我們看一下函數如何來寫:
const getUserInfo = (user: IUser): string => { return `name: ${user.name}, age: ${user.age}`; }; // 正確的調用 getUserInfo({name: "koala", age: 18});
// 錯誤的調用和之前一樣,報錯信息也相同不再說明。
接口中函數的定義再次改造
定義兩個接口:
type IUserInfoFunc = (user: IUser) => string;
interface IUser {
name: string;
age: number;
}
接着我們去定義函數和調用函數即可:
const getUserInfo: IUserInfoFunc = (user) => { return `name: ${user.name}, age: ${user.age}`; };
// 正確的調用
getUserInfo({name: "koala", age: 18});
// 錯誤的調用
getUserInfo();
好處TWO —— 過去我們用 Node.js 寫后端接口
其實這個說明和上面類似,我再提一下,就是想證明 TypeScript 確實挺香的! 寫一個后端接口,我要特意封裝一個工具類,來檢測前端給我傳遞過來的參數,比如下圖中的validate專門用來檢驗參數的函數
但是有了 TypeScript 這個參數檢驗函數可以省略了,我們可以這樣寫:
const goodParams: IGoodsBody = this.ctx.body;
而GoodsBody就是對應參數定義的 interface,比如這個樣子
// -- 查詢列表時候使用的接口 interface IQuery { page: number; rows: number; disabledPage?: boolean; // 是否禁用分頁,true將會忽略`page`和`rows`參數 } // - 商品 export interface IGoodsQuery extends Query { isOnline?: string | number; // 是否出售中的商品 goodsNo?: string; // 商品編號 goodsName?: string; // 商品名稱 }
接口的基礎篇
接口的定義
和 java 語言相同,TypeScript 中定義接口也是使用 interface 關鍵字來定義:
interface IQuery {
page: number;
}
你會發現我都在接口的前面加了一個I,算是個人習慣吧,之前一直寫 java 代碼,另一方面tslint要求,否則會報一個警告,是否加看個人。
接口中定義方法
看上面的接口中,我們定義了 page 常規屬性,定義接口時候不僅僅可以有 屬性,也可以有方法,看下面的例子:
interface IQuery { page: number; findOne(): void; findAll(): void; }
如果我們有一個對象是該接口類型,那么必須包含對應的屬性和方法(無可選屬性情況):
const q: IQuery = { page: 1, findOne() { console.log("findOne"); }, findAll() { console.log("findAll"); }, };
接口中定義屬性
普通屬性
上面的 page 就是普通屬性,如果有一個對象是該接口類型,那么必須包含對應的普通屬性。就不具體說了。
可選屬性
默認情況下一個變量(對象)是對應的接口類型,那么這個變量(對象)必須實現接口中所有的屬性和方法。
但是,開發中為了讓接口更加的靈活,某些屬性我們可能希望設計成可選的(想實現可以實現,不想實現也沒有關系),這個時候就可以使用可選屬性(后面詳細講解函數時,也會講到函數中有可選參數):
interface IQuery { page: number; findOne(): void; findAll(): void; isOnline?: string | number; // 是否出售中的商品 delete?(): void }
上面的代碼中,我們增加了isOnline屬性和delete方法,這兩個都是可選的:
注意:可選屬性如果沒有賦值,那么獲取到的值是
undefined; 對於可選方法,必須先進行判斷,再調用,否則會報錯;
const q: IQuery = { page: 1, findOne() { console.log("findOne"); }, findAll() { console.log("findAll"); }, }; console.log(p.isOnline); // undefined p.delete(); // 不能調用可能是“未定義”的對象。
正確的調用方式如下:
if (p.delete) { p.delete(); }
大家可能會問既然是可選屬性,可有可無的,那么為什么還要定義呢?對比起完全不定義,定義可選屬性主要是:為了讓接口更加的靈活,某些屬性我們可能希望設計成可選,並且如果存在屬性,能約束類型,而這也是十分關鍵的。
只讀屬性
默認情況下,接口中定義的屬性可讀可寫: 但是有一個關鍵字 readonly,定義的屬性值,不可以進行修改,強制修改后報錯。
interface IQuery { readonly page: number; findOne(): void; }
給page屬性加了readonly關鍵字,再給它賦值會報錯。
const q: IQuery = { page: 1, findOne() { console.log("findOne"); }, }; q.page = 10;// Cannot assign to 'page' because it is a read-only property.
接口的高級篇
函數類型接口
Interface 還可以用來規范函數的形狀。Interface 里面需要列出參數列表返回值類型的函數定義。寫法如下:
- 定義了一個函數接口
- 接口接收三個參數並且不返回任何值
- 使用函數表達式來定義這種形狀的函數
interface Func { // ✔️ 定於這個函數接收兩個必選參數都是 number 類型,以及一個可選的字符串參數 desc,這個函數不返回任何值 (x: number, y: number, desc?: string): void } const sum: Func = function (x, y, desc = '') { // const sum: Func = function (x: number, y: number, desc: string): void { // ts類型系統默認推論可以不必書寫上述類型定義 console.log(desc, x + y) } sum(32, 22)
注意:不過上面的接口中只有一個函數,TypeScript 會給我們一個建議,可以使用 type 來定義一個函數的類型:
type Func = (x: number, y: number, desc?: string) => void;
接口的實現
接口除了定義某種類型規范,也可以和其他編程語言一樣,讓一個類去實現某個接口,那么這個類就必須明確去擁有這個接口中的屬性和實現其方法:
下面的代碼中會有關於修飾符的警告,暫時忽略,后面詳細講解 // 定義一個實體接口
interface Entity { title: string; log(): void; }
// 實現這樣一個接口
class Post implements Entity { title: string; constructor(title: string) { this.title = title; } log(): void { console.log(this.title); } }
有些小伙伴的疑問?我定義了一個接口,但是我在繼承這個接口的類中還要寫接口的實現方法,那我不如直接就在這個類中寫實現方法豈不是更便捷,還省去了定義接口?這是一個初學者經常會有疑惑的地方。
解答這個疑惑之前,先記住兩個字,規范!
這個規范可以達到你一看這名字,就知道他是用來干什么的,並且可拓展,可以維護。
-
在代碼設計中,接口是一種規范; 接口通常用於來定義某種規范, 類似於你必須遵守的協議,
-
站在程序角度上說接口只規定了類里必須提供的屬性和方法,從而分離了規范和實現,增強了系統的可拓展性和可維護性;
接口的繼承
和類一樣,接口也能繼承其他的接口。這相當於復制接口的所有成員。接口也是用關鍵字 extends 來繼承。
interface Shape { //定義接口Shape
color: string;
}
interface Square extends Shape { //繼承接口Shape
sideLength: number;
}
一個 interface 可以同時繼承多個 interface ,實現多個接口成員的合並。用逗號隔開要繼承的接口。
interface Shape {
color: string;
}
interface PenStroke {
penWidth: number;
}
interface Square extends Shape, PenStroke {
sideLength: number;
}
需要注意的是,盡管支持繼承多個接口,但是如果繼承的接口中,定義的同名屬性的類型不同的話,是不能編譯通過的。如下代碼:
interface Shape {
color: string;
test: number;
}
interface PenStroke extends Shape{
penWidth: number;
test: string;
}
另外關於繼承還有一點,如果現在有一個類實現了 Square 接口,那么不僅僅需要實現 Square 的方法,也需要實現 Square 繼承自的接口中的方法,實現接口使用 implements 關鍵字 。
可索引類型接口
interface和type的區別
type 可以而 interface 不行
- type 可以聲明基本類型別名,聯合類型,元組等類型
// 基本類型別名 type Name = string // 聯合類型 interface Dog { wong(); } interface Cat { miao(); } type Pet = Dog | Cat // 具體定義數組每個位置的類型 type PetList = [Dog, Pet]
- type 語句中還可以使用 typeof 獲取實例的 類型進行賦值
// 當你想獲取一個變量的類型時,使用 typeof let div = document.createElement('div'); type B = typeof div
- type 其他騷操作
type StringOrNumber = string | number; type Text = string | { text: string }; type NameLookup = Dictionary<string, Person>; type Callback<T> = (data: T) => void; type Pair<T> = [T, T]; type Coordinates = Pair<number>; type Tree<T> = T | { left: Tree<T>, right: Tree<T> };
interface 可以而 type 不行
interface 能夠聲明合並
interface User { name: string age: number } interface User { sex: string } /* User 接口為 { name: string age: number sex: string } */
另外關於type的更多內容,可以查看文檔:TypeScript官方文檔
接口的應用場景總結
在項目中究竟怎么用,開篇已經舉了兩個例子,在這里再簡單寫一點,最近嘗試了一下egg+ts,學習下。在寫查詢參數檢驗的時候,或者返回固定數據的時候,都會用到接口,看一段簡單代碼,已經看完了上面的文章,自己體會下吧。
import User from '../model/user'; import Good from '../model/good'; // 定義基本查詢類型 // -- 查詢列表時候使用的接口 interface Query { page: number; rows: number; disabledPage?: boolean; // 是否禁用分頁,true將會忽略`page`和`rows`參數 } // 定義基本返回類型 type GoodResult<Entity> = { list: Entity[]; total: number; [propName: string]: any; }; // - 商品 export interface GoodsQuery extends Query { isOnline?: string | number; // 是否出售中的商品 goodsNo?: string; // 商品編號 goodsName?: string; // 商品名稱 } export type GoodResult = QueryResult<Good>;
作者:ikoala
鏈接:https://juejin.im/post/5dd1098e51882529f21587db
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
