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

簡介

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

Typescript

什麼是Typescript?

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

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

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

爲何要學習 Typescript?

下面咱們列出了緣由,爲何咱們應該擁抱TypeScript:typescript

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

強類型語言的優點在於靜態類型檢查。歸納來講主要包括如下幾點:數據庫

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

Axios

什麼是 axios?

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

特性

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

來看看 Axios 官方的例子

GET 的請求方式

// 爲給定 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);
  });
複製代碼

POST

axios.post('/user', {
    firstName: 'Fred',
    lastName: 'Flintstone'
  })
  .then(function (response) {
    console.log(response);
  })
  .catch(function (error) {
    console.log(error);
  });
複製代碼

請求方法的別名

爲方便起見,爲全部支持的請求方法提供了別名編程

  • 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 以及一些經常使用的方法, 那接下來咱們就開始進入正題吧element-ui

起手式項目

建立項目

$ vue create my-vue-typescript
複製代碼

上下鍵選擇,空格鍵肯定json

接下來是一些常規選項

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

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

安裝所需包

Element-ui

$ npm i element-ui -S
複製代碼

qs

$ npm i qs -S
複製代碼

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

Axios

$ npm i axios -S
複製代碼

在此我不會爲你們講解太多 Ts 的知識,但在開始以前想讓你們明白 Typescript 中的幾個點,否則無法繼續下去

小知識講堂

類型註解

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

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

# 咱們指定了 greeter 傳入的 person 參數必須是 string 類型
function greeter(person: string) {
    return "Hello, " + person;
}
複製代碼

接口

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

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)
複製代碼

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

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())
}
複製代碼

單例模式

最先接觸單例模式是在學 PHP 的時候,那個時候在尚未使用框架 PHP 引入 Mysql 的時候,我都會把 Mysql 封裝爲一個單例模式的類

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

優勢

  • 在單例模式中,活動的單例只有一個實例,對單例類的全部實例化獲得的都是相同的一個實例。這樣就 防止其它對象對本身的實例化,確保全部的對象都訪問一個實例
  • 單例模式具備必定的伸縮性,類本身來控制實例化進程,類就在改變實例化進程上有相應的伸縮性
  • 提供了對惟一實例的受控訪問
  • 因爲在系統內存中只存在一個對象,所以能夠 節約系統資源,當 須要頻繁建立和銷燬的對象時單例模式無疑能夠提升系統的性能
  • 容許可變數目的實例
  • 避免對共享資源的多重佔用

缺點

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

適用場景

單例模式只容許建立一個對象,所以節省內存,加快對象訪問速度,所以對象須要被公用的場合適合使用,如多個模塊使用同一個數據源鏈接對象等等。如:

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

實現思路:

一個類能返回對象一個引用(永遠是同一個)和一個得到該實例的方法(必須是靜態方法,一般使用getInstance這個名 稱);當咱們調用這個方法時,若是類持有的引用不爲空就返回這個引用,若是類保持的引用爲空就建立該類的實例並將實例的引用賦予該類保持的引用;同時咱們 還將該類的構造函數定義爲私有方法,這樣其餘處的代碼就沒法經過調用該類的構造函數來實例化該類的對象,只有經過該類提供的靜態方法來獲得該類的惟一實例

開始

擼基礎結構

# 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()
複製代碼

自定義實例默認值 requestConfig

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

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 })
    }
  }
複製代碼

請求攔截器 interceptorsRequest

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)
      }
    )
  }
複製代碼

響應攔截器 `interceptorsResponse

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)
      }
    )
  }
複製代碼

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

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)
      }
    }
  }
複製代碼

響應 logs responseLog

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};`
      )
    }
  }
複製代碼

請求方式 POST GET PUT DELETE

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)
    }
  }
  
...

複製代碼

整合代碼

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()

複製代碼

使用方法

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)
複製代碼

結尾

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

相關文章
相關標籤/搜索