學習Typescript 並使用單例模式 組合Vue+Element-ui 封裝 Axios

簡介

本文將實現在 Vue 框架中使用 Typescript + Element-ui 以單例模式個性化封裝 Axios, 滿足我們項目的所需,留個贊再走吧

Typescript

什麼是Typescript?

typescript 是 JavaScript 的強類型版本。然後在編譯期去掉類型和特有語法,生成純粹的 JavaScript 代碼。由於最終在瀏覽器中運行的仍然是 JavaScript,所以 TypeScript 並不依賴於瀏覽器的支持,也並不會帶來兼容性問題。

TypeScript 是 JavaScript 的超集,這意味著他支持所有的 JavaScript 語法。並在此之上對 JavaScript 添加了一些擴展,如 class / interface / module 等。這樣會大大提升代碼的可閱讀性。

與此同時,TypeScript 也是 JavaScript ES6 的超集,Google 的 Angular 2.0 也宣佈採用 TypeScript 進行開發。這更是充分說明了這是一門面向未來並且腳踏實地的語言。

為什麼要學習 Typescript?

下面我們列出了原因,為什麼我們應該擁抱TypeScript:

  1. 完全的面向對象,類和對象。基於此,TypeScript將成為提高開發人員開發效率的利器,它很容易理解和接受。
  2. 在編寫代碼的階段,TypeScript就能夠找到大部分的錯誤,而JavaScript在這方面就沒那麼友好了。要知道,運行時錯誤越少,你的程序的bug就越少
  3. 相比JavaScript,TypeScript的重構也更容易

強類型語言的優勢在於靜態類型檢查。概括來說主要包括以下幾點:

  • 靜態類型檢查
  • IDE 智能提示
  • 代碼重構
  • 可讀性
  • 靜態類型檢查可以避免很多不必要的錯誤, 不用在調試的時候才發現問題

Axios

什麼是 axios?

Axios 是一個基於 promise 的 HTTP 庫,可以用在瀏覽器和 node.js 中。

特性

  • 從瀏覽器中創建 XMLHttpRequests
  • 從 node.js 創建 http 請求
  • 支持 Promise API
  • 攔截請求和響應
  • 轉換請求數據和響應數據
  • 取消請求
  • 自動轉換 JSON 數據
  • 客戶端支持防禦 XSRF

來看看 Axios 官方的例子

GET 的請求方式

<code>// 為給定 ID 的 user 創建請求
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

// 上面的請求也可以這樣做
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
/<code>

POST

<code>axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});

/<code>

請求方法的別名

為方便起見,為所有支持的請求方法提供了別名

  • axios.request(config)
  • axios.get(url[, config])
  • axios.delete(url[, config])
  • axios.head(url[, config])
  • axios.options(url[, config])
  • axios.post(url[, data[, config]])
  • axios.put(url[, data[, config]])
  • axios.patch(url[, data[, config]])

我們大致瞭解了 Axios 以及一些常用的方法, 那接下來我們就開始進入正題吧

起手式項目

創建項目

<code>$ vue create my-vue-typescript
/<code>
學習Typescript 並使用單例模式 組合Vue+Element-ui 封裝 Axios

上下鍵選擇,空格鍵確定

學習Typescript 並使用單例模式 組合Vue+Element-ui 封裝 Axios

接下來是一些常規選項

學習Typescript 並使用單例模式 組合Vue+Element-ui 封裝 Axios

下面是詢問要不要記錄這次配置以便後面直接使用,我們選擇y

學習Typescript 並使用單例模式 組合Vue+Element-ui 封裝 Axios

創建 Utils 文件夾以及 我們今天的主角 request.ts 文件

學習Typescript 並使用單例模式 組合Vue+Element-ui 封裝 Axios

安裝所需包

Element-ui

<code>$ npm i element-ui -S
/<code>

qs

<code>$ npm i qs -S
/<code>

qs 是一個增加了一些安全性的查詢字符串解析和序列化字符串的庫

Axios

<code>$ npm i axios -S
/<code>

在此我不會為大家講解太多 Ts 的知識,但在開始之前想讓大家明白 Typescript 中的幾個點,不然沒法繼續下去

小知識講堂

類型註解

TypeScript裡的類型註解是一種輕量級的為函數或變量添加約束的方式

<code># 我們指定了 hello 這個變量必須是 string 類型
const hello: string = 'Hello World'

# 我們指定了 greeter 傳入的 person 參數必須是 string 類型
function greeter(person: string) {
return "Hello, " + person;
}
/<code>

接口

在TypeScript裡,只在兩個類型內部的結構兼容那麼這兩個類型就是兼容的。 這就允許我們在實現接口時候只要保證包含了接口要求的結構就可以,而不必明確地使用 implements語句

<code>interface IFamilyData {
father: string
mom: string
son: string
}


function getFamily(family: IFamilyData) {
return `爸爸${family.father},媽媽${family.mom},兒子${family.son}`
}

const family = { father: 'Jack', mom: 'Ruth', son: 'Bieber' }

document.body.innerHTML = getFamily(family)
/<code>

TypeScript支持JavaScript的新特性,比如支持基於類的面向對象編程

<code>class Person{
// 增加兩個屬性
name:string
age:number
// 增加可以傳參的構造方法
constructor(name:string,age:number){
this.name = name
this.age = age
}
// 增加一個自定義的普通的打印函數
print(){
return this.name + ':'' + this.age
}

// 使用上面創建的類
// var p = new Person() // 這裡在使用上面的類時沒有傳遞參數是會報錯的,因為上面定義的 constructor 構造方法中存在參數,所以這裡也一定要傳遞參數
var p = new Person('xiaochuan',22)
alert(p.print())
}
/<code>

單例模式

最早接觸單例模式是在學 PHP 的時候,那個時候在還沒有使用框架 PHP 引入 Mysql 的時候,我都會把 Mysql 封裝為一個單例模式的類

單例模式(Singleton),也叫單子模式,是一種常用的軟件設計模式。在應用這個模式時,單例對象的類必須保證只有一個實例存在。許多時候整個系統只需要擁有一個的全局對象,這樣有利於我們協調系統整體的行為。比如在某個服務器程序中,該服務器的配置信息存放在一個文件中,這些配置數據由一個單例對象統一讀取,然後服務進程中的其他對象再通過這個單例對象獲取這些配置信息。這種方式簡化了在複雜環境下的配置管理

優點

  • 在單例模式中,活動的單例只有一個實例,對單例類的所有實例化得到的都是相同的一個實例。這樣就 防止其它對象對自己的實例化,確保所有的對象都訪問一個實例
  • 單例模式具有一定的伸縮性,類自己來控制實例化進程,類就在改變實例化進程上有相應的伸縮性
  • 提供了對唯一實例的受控訪問
  • 由於在系統內存中只存在一個對象,因此可以 節約系統資源,當 需要頻繁創建和銷燬的對象時單例模式無疑可以提高系統的性能
  • 允許可變數目的實例
  • 避免對共享資源的多重佔用

缺點

  • 不適用於變化的對象,如果同一類型的對象總是要在不同的用例場景發生變化,單例就會引起數據的錯誤,不能保存彼此的狀態
  • 由於單利模式中沒有抽象層,因此單例類的擴展有很大的困難
  • 單例類的職責過重,在一定程度上違背了“單一職責原則”
  • 濫用單例將帶來一些負面問題,如為了節省資源將數據庫連接池對象設計為的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;如果實例化的對象長時間不被利用,系統會認為是垃圾而被回收,這將導致對象狀態的丟失

適用場景

單例模式只允許創建一個對象,因此節省內存,加快對象訪問速度,因此對象需要被公用的場合適合使用,如多個模塊使用同一個數據源連接對象等等。如:

  1. 需要頻繁實例化然後銷燬的對象。
  2. 創建對象時耗時過多或者耗資源過多,但又經常用到的對象。
  3. 有狀態的工具類對象。
  4. 頻繁訪問數據庫或文件的對象

實現思路:

一個類能返回對象一個引用(永遠是同一個)和一個獲得該實例的方法(必須是靜態方法,通常使用getInstance這個名 稱);當我們調用這個方法時,如果類持有的引用不為空就返回這個引用,如果類保持的引用為空就創建該類的實例並將實例的引用賦予該類保持的引用;同時我們 還將該類的構造函數定義為私有方法,這樣其他處的代碼就無法通過調用該類的構造函數來實例化該類的對象,只有通過該類提供的靜態方法來得到該類的唯一實例

開始

擼基礎結構

<code># public 公開的
# protected 受保護的
# private 私有的


import http from 'http'
import https from 'https'
import axios, { AxiosResponse, AxiosRequestConfig, CancelTokenStatic } from 'axios'
import { Message, MessageBox } from 'element-ui'
import qs from 'qs'
import { UserModule } from '@/store/modules/user'

// 類名
class Request {
// 屬性
protected baseURL: any = process.env.VUE_APP_BASE_API
protected service: any
protected pending: Array url: string,
cancel: Function
}> = []
protected CancelToken: CancelTokenStatic = axios.CancelToken
protected axiosRequestConfig: AxiosRequestConfig = {}
protected successCode: Array<number> = [200, 201, 204]
private static _instance: Request;

// 構造函數 初始化工作
private constructor() {

}

// 唯一實例
public static getInstance() : Request {}

protected requestConfig(): void {}

protected interceptorsRequest() {}

protected interceptorsResponse(): void {}

protected removePending(config: any): void {}

public async post(url: string, data: any = {}, config: object = {}) {}

public async delete(url: string, config: object = {}) {}

public async put(url: string, data: any = {}, config: object = {}) {}

public async get(url: string, params: any = {}, config: object = {}) {}

protected requestLog(request: any): void {}

protected responseLog(response: any): void {}
}


export default Request.getInstance()
/<number>/<code>

自定義實例默認值 requestConfig

從名字上我們就看的出來這是一個關於配置的方法 小提示: void 表示沒有返回值

<code>protected requestConfig(): void {
this.axiosRequestConfig = {
// baseURL`將自動加在 `url` 前面,除非 `url` 是一個絕對 URL
baseURL: this.baseURL,
// `headers` 是即將被髮送的自定義請求頭
headers: {
timestamp: new Date().getTime(),
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
},
// transformRequest` 允許在向服務器發送前,修改請求數據
transformRequest: [function (data: any) {
//對data進行任意轉換處理
return data;
}],
// `transformResponse` 在傳遞給 then/catch 前,允許修改響應數據
transformResponse: [function(data: AxiosResponse) {
return data
}],
// `paramsSerializer` 是一個負責 `params` 序列化的函數
paramsSerializer: function(params: any) {
return qs.stringify(params, { arrayFormat: 'brackets' })
},
// `timeout` 指定請求超時的毫秒數(0 表示無超時時間)
// 如果請求話費了超過 `timeout` 的時間,請求將被中斷
timeout: 30000,
// `withCredentials` 表示跨域請求時是否需要使用憑證

withCredentials: false,
// `responseType` 表示服務器響應的數據類型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
responseType: 'json',
// `xsrfCookieName` 是用作 xsrf token 的值的cookie的名稱
xsrfCookieName: 'XSRF-TOKEN',
// `xsrfHeaderName` 是承載 xsrf token 的值的 HTTP 頭的名稱
xsrfHeaderName: 'X-XSRF-TOKEN',
// `maxRedirects` 定義在 node.js 中 follow 的最大重定向數目
maxRedirects: 5,
// `maxContentLength` 定義允許的響應內容的最大尺寸
maxContentLength: 2000,
// `validateStatus` 定義對於給定的HTTP 響應狀態碼是 resolve 或 reject promise 。如果 `validateStatus` 返回 `true` (或者設置為 `null` 或 `undefined`),promise 將被 resolve; 否則,promise 將被 rejecte
validateStatus: function(status: number) {
return status >= 200 && status < 300
},
// `httpAgent` 和 `httpsAgent` 分別在 node.js 中用於定義在執行 http 和 https 時使用的自定義代理。允許像這樣配置選項:
// `keepAlive` 默認沒有啟用
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true })
}
}
/<code>

請求攔截器 interceptorsRequest

<code>protected interceptorsRequest() {
this.service.interceptors.request.use(
(config: any) => {
if (UserModule.token) {
config.headers['authorization'] = UserModule.token
}
return config
},
(error: any) => {
return Promise.reject(error)
}
)

}
/<code>

響應攔截器 `interceptorsResponse

<code>protected interceptorsResponse(): void {
this.service.interceptors.response.use(
(response: any) => {
if (this.successCode.indexOf(response.status) === -1) {
Message({
message: response.data.message || 'Error',
type: 'error',
duration: 5 * 1000
})
if (response.data.code === 401) {
MessageBox.confirm(
'你已被登出,可以取消繼續留在該頁面,或者重新登錄',
'確定登出',
{
confirmButtonText: '重新登錄',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
UserModule.ResetToken()
location.reload()
})
}
return Promise.reject(new Error(response.message || 'Error'))
} else {
return response.data
}
},
(error: any) => {
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
}
/<code>

重複點擊取消上一次請求 removePending

<code>protected removePending(config: any): void {
for (let p in this.pending) {
let item: any = p
let list: any = this.pending[p]
if (list.url === `${config.url}/${JSON.stringify(config.data)}&request_type=${config.method}`) {
list.cancel()
this.pending.splice(item, 1)
}
}
}
/<code>

響應 logs responseLog

<code>protected responseLog(response: any): void {
if (process.env.NODE_ENV === 'development') {
const randomColor = `rgba(${Math.round(Math.random() * 255)},${Math.round(
Math.random() * 255
)},${Math.round(Math.random() * 255)})`
console.log(
'%c┍------------------------------------------------------------------┑',
`color:${randomColor};`
)
console.log('| 請求地址:', response.config.url)
console.log('| 請求參數:', qs.parse(response.config.data))
console.log('| 返回數據:', response.data)
console.log(
'%c┕------------------------------------------------------------------┙',
`color:${randomColor};`
)
}
}
/<code>
學習Typescript 並使用單例模式 組合Vue+Element-ui 封裝 Axios

請求方式 POST GET PUT DELETE

<code>public async post(url: string, data: any = {}, config: object = {}) {
try {
const result = await this.service.post(url, qs.stringify(data), config)
return result.data
} catch (error) {
console.error(error)
}
}

public async delete(url: string, config: object = {}) {
try {
await this.service.delete(url, config)
} catch (error) {
console.error(error)
}
}

...
/<code>

整合代碼

<code>import http from 'http'
import https from 'https'
import axios, { AxiosResponse, AxiosRequestConfig, CancelTokenStatic } from 'axios'
import { Message, MessageBox } from 'element-ui'
import qs from 'qs'
import { UserModule } from '@/store/modules/user'

class Request {
protected baseURL: any = process.env.VUE_APP_BASE_API
protected service: any = axios
protected pending: Array url: string,
cancel: Function
}> = []
protected CancelToken: CancelTokenStatic = axios.CancelToken
protected axiosRequestConfig: AxiosRequestConfig = {}
protected successCode: Array<number> = [200, 201, 204]
private static _instance: Request;

constructor() {
this.requestConfig()
this.service = axios.create(this.axiosRequestConfig)
this.interceptorsRequest()
this.interceptorsResponse()
}


public static getInstance() : Request {
// 如果 instance 是一個實例 直接返回, 如果不是 實例化後返回
this._instance || (this._instance = new Request())
return this._instance
}

protected requestConfig(): void {
this.axiosRequestConfig = {
baseURL: this.baseURL,
headers: {
timestamp: new Date().getTime(),
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
},
transformRequest: [obj => qs.stringify(obj)],
transformResponse: [function(data: AxiosResponse) {
return data
}],
paramsSerializer: function(params: any) {
return qs.stringify(params, { arrayFormat: 'brackets' })
},
timeout: 30000,
withCredentials: false,
responseType: 'json',
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
maxRedirects: 5,
maxContentLength: 2000,
validateStatus: function(status: number) {
return status >= 200 && status < 500
},
httpAgent: new http.Agent({ keepAlive: true }),
httpsAgent: new https.Agent({ keepAlive: true })
}
}

protected interceptorsRequest() {
this.service.interceptors.request.use(
(config: any) => {
this.removePending(config)
config.CancelToken = new this.CancelToken((c: any) => {
this.pending.push({ url: `${config.url}/${JSON.stringify(config.data)}&request_type=${config.method}`, cancel: c })
})
if (UserModule.token) {
config.headers['authorization'] = UserModule.token
}
this.requestLog(config)
return config

},
(error: any) => {
return Promise.reject(error)
}
)
}

protected interceptorsResponse(): void {
this.service.interceptors.response.use(
(response: any) => {
this.responseLog(response)
this.removePending(response.config)
if (this.successCode.indexOf(response.status) === -1) {
Message({
message: response.data.message || 'Error',
type: 'error',
duration: 5 * 1000
})
if (response.data.code === 401) {
MessageBox.confirm(
'你已被登出,可以取消繼續留在該頁面,或者重新登錄',
'確定登出',
{
confirmButtonText: '重新登錄',
cancelButtonText: '取消',
type: 'warning'
}
).then(() => {
UserModule.ResetToken()
location.reload()
})
}
return Promise.reject(new Error(response.message || 'Error'))
} else {
return response.data
}
},
(error: any) => {
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
}


protected removePending(config: any): void {
for (let p in this.pending) {
let item: any = p
let list: any = this.pending[p]
if (list.url === `${config.url}/${JSON.stringify(config.data)}&request_type=${config.method}`) {
list.cancel()
console.log('=====', this.pending)
this.pending.splice(item, 1)
console.log('+++++', this.pending)
}
}
}

public async post(url: string, data: any = {}, config: object = {}) {
try {
const result = await this.service.post(url, qs.stringify(data), config)
return result.data
} catch (error) {
console.error(error)
}
}

public async delete(url: string, config: object = {}) {
try {
await this.service.delete(url, config)
} catch (error) {
console.error(error)
}
}

public async put(url: string, data: any = {}, config: object = {}) {
try {
await this.service.put(url, qs.stringify(data), config)
} catch (error) {
console.error(error)
}
}

public async get(url: string, parmas: any = {}, config: object = {}) {
try {
await this.service.get(url, parmas, config)
} catch (error) {
console.error(error)
}
}

protected requestLog(request: any): void {
}

protected responseLog(response: any): void {
if (process.env.NODE_ENV === 'development') {
const randomColor = `rgba(${Math.round(Math.random() * 255)},${Math.round(
Math.random() * 255
)},${Math.round(Math.random() * 255)})`
console.log(
'%c┍------------------------------------------------------------------┑',
`color:${randomColor};`
)
console.log('| 請求地址:', response.config.url)
console.log('| 請求參數:', qs.parse(response.config.data))
console.log('| 返回數據:', response.data)
console.log(
'%c┕------------------------------------------------------------------┙',
`color:${randomColor};`
)
}
}
}

export default Request.getInstance()
/<number>/<code>

使用方法

<code>import Request from '@/utils/request'
import { ADMIN_LOGIN_API, ADMIN_USER_INFO_API } from '@/api/interface'

interface ILoginData {
username: string
password: string
}

export const login = (params: ILoginData) => Request.post(ADMIN_LOGIN_API, params)
export const getUserInfo = () => Request.get(ADMIN_USER_INFO_API)
/<code>
學習Typescript 並使用單例模式 組合Vue+Element-ui 封裝 Axios

結尾

各位大哥大姐留個贊吧 O(∩_∩)O哈哈~ 到此就結束了,我也是第一次學習 ts 並且 封裝 axios 寫的不好,下方留言指出,謝謝。


分享到:


相關文章: