Vue中axios的封裝

axios 是 Vue 官方推薦的一個 HTTP 庫,用 axios 官方簡介來介紹它藏澳,就是:Axios 是一個基于 promise 的 HTTP 庫耀找,可以用在瀏覽器和 node.js 中。

作為一個優(yōu)秀的 HTTP 庫野芒,axios 打敗了曾經(jīng)由 Vue 官方團(tuán)隊維護(hù)的 vue-resource,獲得了 Vue 作者尤小右的大力推薦狞悲,成為了 Vue 項目中 HTTP 庫的最佳選擇。

雖然效诅,axios 是個優(yōu)秀的 HTTP 庫,但是乱投,直接在項目中使用并不是那么方便戚炫,所以,我們需要對其進(jìn)行一定程度上的配置封裝双肤,減少重復(fù)代碼,方便調(diào)用茅糜。下面素挽,我們就來聊聊 Vue 中 axios 的封裝。

開始

其實,網(wǎng)上關(guān)于 axios 封裝的代碼不少缩赛,但是大部分都是在入口文件(main.js)中進(jìn)行 axios 全局對象屬性定義的形式進(jìn)行配置,類似于如下代碼:

axios.defaults.timeout = 10000

該方案有兩個不足辩昆,首先,axios 封裝代碼耦合進(jìn)入入口文件汁针,不方便后期維護(hù);其次施无,使用 axios 全局對象屬性定義的方式進(jìn)行配置尉辑,代碼過于零散。

針對問題一隧魄,我使用了 Vue 源碼結(jié)構(gòu)中的一大核心思想——將功能拆分為文件,方便后期的維護(hù)购啄。單獨創(chuàng)建一個 http.js 或者 http.ts 文件,在文件中引入 axios 并對其進(jìn)行封裝配置狮含,最后將其導(dǎo)出并掛載到 Vue 的原型上即可。此時几迄,每次修改 axios 配置,只需要修改對應(yīng)的文件即可映胁,不會影響到不相關(guān)的功能。

針對問題二坑填,采用 axios 官方推薦的,通過配置項創(chuàng)建 axios 實例的方式進(jìn)行配置封裝脐瑰。

代碼如下:

// http.js
import axios from 'axios'
// 創(chuàng)建 axios 實例
const service = axios.create({
  // 配置項
})

根據(jù)環(huán)境設(shè)置 baseURL

baseURL 屬性是請求地址前綴,將自動加在 url 前面苍在,除非 url 是個絕對地址。正常情況下忌穿,在開發(fā)環(huán)境下和生產(chǎn)模式下有著不同的 baseURL,所以掠剑,我們需要根據(jù)不同的環(huán)境切換不同的 baseURL。

在開發(fā)模式下朴译,由于有著 devServer 的存在,需要根據(jù)固定的 url 前綴進(jìn)行請求地址重寫眠寿,所以,在開發(fā)環(huán)境下盯拱,將 baseURL 設(shè)為某個固定的值,比如:/apis狡逢。

在生產(chǎn)模式下,根據(jù) Java 模塊的請求前綴的不同奢浑,可以設(shè)置不同的 baseURL。

具體代碼如下:

// 根據(jù) process.env.NODE_ENV 區(qū)分狀態(tài)雀彼,切換不同的 baseURL
const service = axios.create({
    baseURL: process.env.NODE_ENV === 'production' ? `/java` : '/apis',
})

統(tǒng)一設(shè)置請求頭

在這里和大家聊一個問題,什么是封裝袜刷?在我看來,封裝是通過更少的調(diào)用代碼覆蓋更多的調(diào)用場景水泉。

由于,大部分情況下,請求頭都是固定的钢拧,只有少部分情況下,會需要一些特殊的請求頭源内,所以份殿,在這里嗽交,我采用的方案是,將普適性的請求頭作為基礎(chǔ)配置夫壁。當(dāng)需要特殊請求頭時,將特殊請求頭作為參數(shù)傳入盒让,覆蓋基礎(chǔ)配置。

代碼如下:

const service = axios.create({
    ...
    headers: {
        get: {
          'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
          // 在開發(fā)中邑茄,一般還需要單點登錄或者其他功能的通用請求頭,可以一并配置進(jìn)來
        },
        post: {
          'Content-Type': 'application/json;charset=utf-8'
          // 在開發(fā)中肺缕,一般還需要單點登錄或者其他功能的通用請求頭,可以一并配置進(jìn)來
        }
  },
})

跨域浮梢、超時、響應(yīng)碼處理

axios 中黔寇,提供是否允許跨域的屬性——withCredentials,以及配置超時時間的屬性——timeout缝裤,通過這兩個屬性,可以輕松處理跨域和超時的問題憋飞。

下面,我們來說說響應(yīng)碼處理:

axios 提供了 validateStatus 屬性榛做,用于定義對于給定的HTTP 響應(yīng)狀態(tài)碼是 resolve 或 reject promise内狸。所以,正常設(shè)置的情況下昆淡,我們會將狀態(tài)碼為 2 系列或者 304 的請求設(shè)為 resolve 狀態(tài),其余為 reject 狀態(tài)昂灵。結(jié)果就是舞萄,我們可以在業(yè)務(wù)代碼里管削,使用 catch 統(tǒng)一捕獲響應(yīng)錯誤的請求,從而進(jìn)行統(tǒng)一處理含思。

但是,由于我在代碼里面使用了 async-await茸俭,而眾所周知,async-await 捕獲 catch 的方式極為麻煩调鬓,所以,在此處腾窝,我選擇將所有響應(yīng)都設(shè)為 resolve 狀態(tài),統(tǒng)一在 then 處理虹脯。

此部分代碼如下:

const service = axios.create({
    // 跨域請求時是否需要使用憑證
    withCredentials: true,
    // 請求 30s 超時
    timeout: 30000,
    validateStatus: function () {
        // 使用async-await,處理reject情況較為繁瑣循集,所以全部返回resolve,在業(yè)務(wù)代碼中處理異常
        return true
    },
})

請求咒彤、響應(yīng)處理

在不使用 axios 的情況下,每次請求或者接受響應(yīng)镶柱,都需要將請求或者響應(yīng)序列化。

而在 axios 中歇拆, transformRequest 允許在向服務(wù)器發(fā)送請求前,修改請求數(shù)據(jù)故觅;transformResponse 在傳遞給 then/catch 前,允許修改響應(yīng)數(shù)據(jù)输吏。

通過這兩個鉤子,可以省去大量重復(fù)的序列化代碼评也。

代碼如下:

const service = axios.create({
    // 在向服務(wù)器發(fā)送請求前,序列化請求數(shù)據(jù)
    transformRequest: [function (data) {
        data = JSON.stringify(data)
        return data
    }],
    // 在傳遞給 then/catch 前盗迟,修改響應(yīng)數(shù)據(jù)
    transformResponse: [function (data) {
        if (typeof data === 'string' && data.startsWith('{')) {
            data = JSON.parse(data)
        }
        return data
    }]
})

攔截器

攔截器,分為請求攔截器以及響應(yīng)攔截器罚缕,分別在請求或響應(yīng)被 then 或 catch 處理前攔截它們。

之前提到過邮弹,由于 async-await 中 catch 難以處理的問題黔衡,所以將出錯的情況也作為 resolve 狀態(tài)進(jìn)行處理盟劫。但這帶來了一個問題,請求或響應(yīng)出錯的情況下侣签,結(jié)果沒有數(shù)據(jù)協(xié)議中定義的 msg 字段(消息)急迂。所以,我們需要在出錯的時候僚碎,手動生成一個符合返回格式的返回數(shù)據(jù)。

由于勺阐,在業(yè)務(wù)中卷中,沒有需要在請求攔截器中做額外處理的需求仓坞,所以,請求攔截器的 resolve 狀態(tài)无埃,只需直接返回就可以了。

請求攔截器代碼如下:

// 請求攔截器
service.interceptors.request.use((config) => {
    return config
}, (error) => {
    // 錯誤拋到業(yè)務(wù)代碼
    error.data = {}
    error.data.msg = '服務(wù)器異常嫉称,請聯(lián)系管理員灵疮!'
    return Promise.resolve(error)
})

再來聊聊響應(yīng)攔截器织阅,還是之前的那個問題荔棉,除了請求或響應(yīng)錯誤闹炉,還有一種情況也會導(dǎo)致返回的消息體不符合協(xié)議規(guī)范润樱,那就是狀態(tài)碼不為 2 系列或 304 時。此時嗅钻,我們還是需要做一樣的處理——手動生成一個符合返回格式的返回數(shù)據(jù)。但是养篓,有一點不一樣,我們還需要根據(jù)不同的狀態(tài)碼生成不同的提示信息赂蕴,以方便處理上線后的問題。

響應(yīng)攔截器代碼如下:

// 根據(jù)不同的狀態(tài)碼睡腿,生成不同的提示信息
const showStatus = (status) => {
    let message = ''
    // 這一坨代碼可以使用策略模式進(jìn)行優(yōu)化
    switch (status) {
        case 400:
            message = '請求錯誤(400)'
            break
        case 401:
            message = '未授權(quán),請重新登錄(401)'
            break
        case 403:
            message = '拒絕訪問(403)'
            break
        case 404:
            message = '請求出錯(404)'
            break
        case 408:
            message = '請求超時(408)'
            break
        case 500:
            message = '服務(wù)器錯誤(500)'
            break
        case 501:
            message = '服務(wù)未實現(xiàn)(501)'
            break
        case 502:
            message = '網(wǎng)絡(luò)錯誤(502)'
            break
        case 503:
            message = '服務(wù)不可用(503)'
            break
        case 504:
            message = '網(wǎng)絡(luò)超時(504)'
            break
        case 505:
            message = 'HTTP版本不受支持(505)'
            break
        default:
            message = `連接出錯(${status})!`
    }
    return `${message}应闯,請檢查網(wǎng)絡(luò)或聯(lián)系管理員挂捻!`
}

// 響應(yīng)攔截器
service.interceptors.response.use((response) => {
    const status = response.status
    let msg = ''
    if (status < 200 || status >= 300) {
        // 處理http錯誤,拋到業(yè)務(wù)代碼
        msg = showStatus(status)
        if (typeof response.data === 'string') {
            response.data = { msg }
        } else {
            response.data.msg = msg
        }
    }
    return response
}, (error) => {
    // 錯誤拋到業(yè)務(wù)代碼
    error.data = {}
    error.data.msg = '請求超時或服務(wù)器異常刻撒,請檢查網(wǎng)絡(luò)或聯(lián)系管理員!'
    return Promise.resolve(error)
})

tips:友情提示声怔,上面那一坨 switch-case 代碼,可以使用策略模式進(jìn)行優(yōu)化~

支持 TypeScript

由于前段時間醋火,我在部門內(nèi)推了 TypeScript,為了滿足自己的強(qiáng)迫癥芥驳,將所有 js 文件改寫為了 ts 文件。由于 axios 本身有 TypeScript 相關(guān)的支持假抄,所以只需要把對應(yīng)的類型導(dǎo)入,然后賦值即可宿饱。

完整代碼

// http.ts
import axios, { AxiosRequestConfig, AxiosResponse } from 'axios'

const showStatus = (status: number) => {
  let message = ''
  switch (status) {
    case 400:
      message = '請求錯誤(400)'
      break
    case 401:
      message = '未授權(quán),請重新登錄(401)'
      break
    case 403:
      message = '拒絕訪問(403)'
      break
    case 404:
      message = '請求出錯(404)'
      break
    case 408:
      message = '請求超時(408)'
      break
    case 500:
      message = '服務(wù)器錯誤(500)'
      break
    case 501:
      message = '服務(wù)未實現(xiàn)(501)'
      break
    case 502:
      message = '網(wǎng)絡(luò)錯誤(502)'
      break
    case 503:
      message = '服務(wù)不可用(503)'
      break
    case 504:
      message = '網(wǎng)絡(luò)超時(504)'
      break
    case 505:
      message = 'HTTP版本不受支持(505)'
      break
    default:
      message = `連接出錯(${status})!`
  }
  return `${message}刑棵,請檢查網(wǎng)絡(luò)或聯(lián)系管理員!`
}

const service = axios.create({
  // 聯(lián)調(diào)
  baseURL: process.env.NODE_ENV === 'production' ? `/` : '/apis',
  headers: {
    get: {
      'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
    },
    post: {
      'Content-Type': 'application/json;charset=utf-8'
    }
  },
  // 是否跨站點訪問控制請求
  withCredentials: true,
  timeout: 30000,
  transformRequest: [(data) => {
    data = JSON.stringify(data)
    return data
  }],
  validateStatus () {
    // 使用async-await蛉签,處理reject情況較為繁瑣碍舍,所以全部返回resolve,在業(yè)務(wù)代碼中處理異常
    return true
  },
  transformResponse: [(data) => {
    if (typeof data === 'string' && data.startsWith('{')) {
      data = JSON.parse(data)
    }
    return data
  }]
})

// 請求攔截器
service.interceptors.request.use((config: AxiosRequestConfig) => {
    return config
}, (error) => {
    // 錯誤拋到業(yè)務(wù)代碼
    error.data = {}
    error.data.msg = '服務(wù)器異常片橡,請聯(lián)系管理員!'
    return Promise.resolve(error)
})

// 響應(yīng)攔截器
service.interceptors.response.use((response: AxiosResponse) => {
    const status = response.status
    let msg = ''
    if (status < 200 || status >= 300) {
        // 處理http錯誤淮野,拋到業(yè)務(wù)代碼
        msg = showStatus(status)
        if (typeof response.data === 'string') {
            response.data = {msg}
        } else {
            response.data.msg = msg
        }
    }
    return response
}, (error) => {
    // 錯誤拋到業(yè)務(wù)代碼
    error.data = {}
    error.data.msg = '請求超時或服務(wù)器異常,請檢查網(wǎng)絡(luò)或聯(lián)系管理員经瓷!'
    return Promise.resolve(error)
})

export default service
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末洞难,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子队贱,更是在濱河造成了極大的恐慌色冀,老刑警劉巖柱嫌,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異编丘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)癣防,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掌眠,“玉大人,你說我怎么就攤上這事级遭。” “怎么了挫鸽?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盔沫。 經(jīng)常有香客問我,道長枫匾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任谴忧,我火速辦了婚禮,結(jié)果婚禮上沾谓,老公的妹妹穿的比我還像新娘。我一直安慰自己均驶,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布辣恋。 她就那樣靜靜地躺著,像睡著了一般伟骨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上携狭,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天回俐,我揣著相機(jī)與錄音,去河邊找鬼仅颇。 笑死,一個胖子當(dāng)著我的面吹牛忘瓦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼蝙场,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了售滤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤完箩,失蹤者是張志新(化名)和其女友劉穎拉队,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體氏仗,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡夺鲜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了慷蠕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片食呻。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖仅胞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情干旧,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布椎眯,位于F島的核電站,受9級特大地震影響编整,放射性物質(zhì)發(fā)生泄漏舔稀。R本人自食惡果不足惜掌测,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧淆两,春花似錦、人聲如沸拂酣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赵颅。三九已至,卻和暖如春饺谬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背募寨。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留仪缸,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓列肢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瓷马。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 伙伴: 時光流逝千余日夜自沧,太匆匆树瞭。 一路走來,所得幾何晒喷,所失幾何孝偎。 很多事過去了,很多人離...
    黃孩兒閱讀 323評論 2 0
  • 本人長痘已經(jīng)六七年了寺旺,剛開始長痘是在初中,剛開始的時候只是在鼻子長势决,就是長那種白白的阻塑,但是長得很慢,慢慢的果复,痘痘開...
    再看你一樣閱讀 188評論 0 0