JavaScript 中定義一個函數,用來獲取一個用戶的姓名和年齡的字符串:javascript
const getUserInfo = function(user) {
return name: ${user.name}, age: ${user.age}
}
複製代碼
函數調用:前端
getUserInfo({name: "koala", age: 18})
複製代碼
這對於咱們以前在寫 JavaScript 的時候,再正常不過了,可是若是這個 getUserInfo
在多人開發過程當中,若是它是個公共函數
,多個開發者都會調用,若是不是每一個人點進來看函數對應註釋
,可能會出現如下問題:java
// 錯誤的調用
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 是弱類型的語言,因此並不會對咱們傳入的代碼進行任何的檢測,有些錯你本身都說不清楚,可是就出了問題。git
const getUserInfo = (user: {name: string, age: number}): string => {
return `name: ${user.name} age: ${user.age}`;
};
複製代碼
正確的調用是以下的方式:程序員
getUserInfo({name: "kaola", age: 18});
複製代碼
若是調用者出現了錯誤的調用,那麼 TypeScript
會直接給出錯誤的提示信息:github
// 錯誤的調用
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
的必要性。typescript
使用 interface 對 user 的類型進行重構。數據庫
咱們先定義一個 IUser
接口:編程
// 先定義一個接口
interface IUser {
name: string;
age: number;
}
複製代碼
接下來咱們看一下函數
如何來寫:c#
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();
複製代碼
其實這個說明和上面相似,我再提一下,就是想證實 TypeScript 確實挺香的! 寫一個後端接口,我要特地封裝一個工具類,來檢測前端給我傳遞過來的參數,好比下圖中的validate
專門用來檢驗參數的函數
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; // 商品名稱
}
複製代碼
好的,說了他的幾個好處以後,咱們開始學習interface知識吧!還有不少優勢哦!
做者簡介:koala,專一完整的 Node.js 技術棧分享,從 JavaScript 到 Node.js,再到後端數據庫,祝您成爲優秀的高級 Node.js 工程師。【程序員成長指北】做者,Github 博客開源項目 github.com/koala-codin…
和 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
關鍵字 。
// 基本類型別名
type Name = string
// 聯合類型
interface Dog {
wong();
}
interface Cat {
miao();
}
type Pet = Dog | Cat
// 具體定義數組每一個位置的類型
type PetList = [Dog, Pet]
複製代碼
// 當你想獲取一個變量的類型時,使用 typeof
let div = document.createElement('div');
type B = typeof div
複製代碼
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 可以聲明合併
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>;
複製代碼
TypeScript
仍是挺香的,預告一篇明天的發文吧,TypeScript
強大的類型別名。今天就分享這麼多,若是對分享的內容感興趣,能夠關注公衆號「程序員成長指北」,加我微信(coder_qi),拉你進技術羣,長期交流學習。
require時,exports和module.exports的區別你真的懂嗎