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
專門用來檢驗參數的函數
可是有了 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; // 商品名稱 }
好的,說了他的幾個好處以後,咱們開始學習interface知識吧!還有不少優勢哦!
做者簡介:koala,專一完整的 Node.js 技術棧分享,從 JavaScript 到 Node.js,再到後端數據庫,祝您成爲優秀的高級 Node.js 工程師。【程序員成長指北】做者,Github 博客開源項目 https://github.com/koala-codi...
和 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),拉你進技術羣,長期交流學習。
https://juejin.im/post/5c8fbf...
https://www.teakki.com/p/57df...
https://juejin.im/post/5c2723...
https://mp.weixin.qq.com/s/aj...
http://cw.hubwiz.com/card/c/5...
require時,exports和module.exports的區別你真的懂嗎)