基於Vue對axios進行上層封裝
基於Vue對axios進行上層封裝
這的裡的很多東西都是基於上一節 axios 講解的,如果你不是對axios瞭解,請先看該文章
一、要真正拿axios進行實戰之前要了解一些細節上的東西
(1) 先搞清楚請求傳送的配置和攔截問題
-
axios的配置是可以合併的也有優先順序,全域性的axios.create建立的axios例項會自動吸取全域性axios的配置
-
修改任意axios例項的配置,都可以通過axios例項的defaults屬性獲取配置物件,再修改對應的配置項
-
axios的攔截器並不是可合併的,在每個單獨的例項上的interceptor對應的過程(request,response)上呼叫use表示在單個例項新增對應攔截器;同樣全域性的axios.create建立的axios例項並不會
-
axios的攔截器的傳入的攔截器其實內部和呼叫then方法一樣,一個onFulfilled和onRejected函式,所以要注意函式返回的結果,或者是否需要丟擲錯誤以改變呼叫順序
(2) 令人頭疼的Content-Type
axios預設是會自己對傳送請求方法不同,和傳入資料(請求體)的不同變更其 Content-Type,但是有的時候,這種預設的Content-Type並不是我們所期待的,於是需要手動配置。
先說一下不同方式axios的預設Content-Type
- GET 沒有,只有有請求體的請求方式才存在 Content-Type
- POST、PUT、PATCH
- DELETE text/plain 比較奇怪的是 Params 和Data都能傳遞引數
如果你希望使用Restful規範,delete只是text/plain 方式的肯定在後臺接受引數會產生問題,這裡兩種解決反式
- DELETE 的資料存放在 Params中,後端從URL獲取,自然不會受到text/plain的影響
- DELETE 的資料存放在 Data 中,Content-Type設定為application/x-www-form-urlencoded,後端可以正常的從請求體獲取資料
其它的請求方法表現的都計較正常(正常情況下,如果你有特殊需求另外分析)
(3) 官網文件請求配置一些有點爭議的地方
-
baseURL 說是識別相對地址,如果是相對路徑就自動的加上,否則不加。實際測試中 baseURL 對 請求路徑是http/https/域名開頭的直接忽略,識別為絕對地址;對請求其它請求路徑通過類似於path.join函式一樣,於是相對路徑帶 “/” 還是不帶可以隨意
-
transformRequest 官方說只是最終影響POST、PUT、PATCH,但是實際測試有點奇怪:無論什麼方法都會呼叫transformRequest的函式佇列,但是隻有擁有請求體的請求才會真正的處理資料併發出,得出結論:只要該請求允許攜帶請求體,transformRequest就有效
(4) 值得注意的幾個細節
-
請求配置中,responseType 預設是JSON,這也是為什麼then返回的資料可以是物件
-
請求配置中,paramsSerializer 負責序列化 Params的函式,它是有預設值,這也是為什麼傳入的Params物件最終以字串拼接在URL上
-
withCredentials 這幾乎是最重要的一點,很關鍵:withCredentials` 表示跨域請求時是否需要使用憑證,這個憑證就代表這Cookie和Token是否能被後端正確接受和寫回給前端。預設的值為false表示跨域不攜帶。注意的是:只有在跨域情況下這個欄位才有效果,非跨域下一定攜帶憑證 (如果有的話,並且不是手動去除)
在 Ajax跨域和Nginx反向代理 那篇有說明 withCredentials 具體怎麼使用
二、開始上層封裝
(1) 剝離攔截器
新建一個 axios-interceptors.js 檔案
將你需要的攔截器寫出這種形式
//你可能對option引數不太理解,等看完下一小段,你就能明白
const reqCommon = {
/**
* 傳送請求之前做些什麼
* @param config axios config
* @returns {*}
*/
onFulfilled(config, option) {
return config
},
/**
* 請求出錯時做點什麼
* @param error 錯誤物件
* @returns {Promise<never>}
*/
onRejected(error, option) {
return Promise.reject(error)
}
}
//響應攔截
const resCommon ={
onFulfilled(response, option) {
return response
},
onRejected(error, option) {
const { response, request } = error
if (response) { //響應是否存在
switch (response.status) {
case 400:
error.message = '錯誤請求'
break
case 401:
error.message = '未授權,請重新登入'
break
case 403:
error.message = '拒絕訪問'
break
case 404:
error.message = '請求錯誤,未找到該資源'
break
case 405:
error.message = '請求方法未允許'
break
case 408:
error.message = '請求超時'
break
case 500:
error.message = '伺服器端出錯'
break
case 501:
error.message = '網路未實現'
break
case 502:
error.message = '網路錯誤'
break
case 503:
error.message = '服務不可用'
break
case 504:
error.message = '網路超時'
break
case 505:
error.message = 'http版本不支援該請求'
break
default:
error.message = `未知錯誤${error.response.status}`
}
}else if(request){// 響應不存在,很可能是請求都未發出
if(!window.navigator.onLine) { //發現是斷網了
error.message = "網路未連線"
}
}else {
error.message = "配置請求發生了錯誤"
}
return Promise.reject(error)
}
}
export default {
request: [reqCommon], // 請求攔截
response: [resCommon] // 響應攔截
}
可以看到request佇列中的元素是一個物件,物件有兩個方法,都將在被axios例項攔截時呼叫use方法傳入的兩個引數,表示成功和失敗;response佇列也是如此
(2) 生成 loadInterceptors => 載入攔截器函式
//httpRequest => axios例項;interceptors => 分離出來的攔截器;option => 閉包引數
export function loadInterceptors(httpRequest, interceptors, option) {
const {request, response} = interceptors
// 載入請求攔截器
request.forEach(item => {
let {onFulfilled, onRejected} = item
if (!onFulfilled || typeof onFulfilled !== 'function') {
onFulfilled = config => config
}
if (!onRejected || typeof onRejected !== 'function') {
onRejected = error => Promise.reject(error)
}
httpRequest.interceptors.request.use(
//生成閉包!!!這樣在攔截器呼叫的時候能夠有option引數
config => onFulfilled(config, option),
config => onRejected(config, option)
)
})
// 載入響應攔截器
response.forEach(item => {
let {onFulfilled, onRejected} = item
if (!onFulfilled || typeof onFulfilled !== 'function') {
onFulfilled = response => response
}
if (!onRejected || typeof onRejected !== 'function') {
onRejected = error => Promise.reject(error)
}
httpRequest.interceptors.request.use(
config => onFulfilled(config, option),
config => onRejected(config, option)
)
})
}
(2) 生成axios例項,並配置對應的攔截器
//config生成新的axios例項的配置;interceptors要繫結的攔截器;
//requestMethods在例項上繫結的自定義方法陣列
function createAxios({
config, interceptors, requestMethods
}) {
//建立axios例項
const httpRequest = axios.create(config)
//載入攔截器,並繫結到對於的axios例項
loadInterceptors(httpRequest, interceptors)
//遍歷方法陣列
for(let method of requestMethods) {
if(method === 'upload') {
httpRequest['$Upload'] = requestMethodCreator('post')
continue
}
if(method === 'delete') {
httpRequest['$' + method.toUpperCase()] = requestMethodCreator(method, {
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
})
continue
}
httpRequest['$' + method.toUpperCase()] = requestMethodCreator(method)
}
return httpRequest
}
(3) 一次性的自定義攔截器 => beforeRequest、onRequest、atferRequest
//匯出請求方法建立器
function requestMethodCreator(method, outConfig) {
return function baseRequestMethod(API, body, option = {}) {
let { beforeRequest, onRequest, atferRequest, ...innerConfig } = option
//寫一個自己的攔截器
const defaultConfig = {
method,
url: API,
}
//選擇請求體
defaultConfig[method === 'get'? 'params' : 'data'] = body
//合併配置
Object.assign(defaultConfig, innerConfig, outConfig)
//執行自定義攔截
beforeRequest && beforeRequest()
let result = this(defaultConfig)
//真正傳送請求
if(onRequest) {
result = result.then(onRequest, errorReason => Promise.reject(errorReason))
if(atferRequest) {
result = result.finally(atferRequest)
}
}
return result
}
}
(4) 使用匯出utils的http-request.js檔案
import axios from "axios";
import qs from "qs";
import {createAxios} from './create-axios'
import interceptors from './axios-interceptors'
axios.defaults.timeout = 3000
axios.defaults.responseType = 'json'
axios.defaults.transformRequest = [function (data){
console.log(data)
data = {message: '被修改了'}
if(data instanceof FormData) return data
data = qs.stringify(data)
return data;
}]
//服務請求路徑字首
const {
VUE_APP_SERVICE_PROXY_URL: SERVICE_PROXY_URL,
VUE_APP_SERVICE_API_PREFIX: SERVICE_API_PREFIX,
VUE_APP_UPLOAD_PROXY_URL: UPLOAD_PROXY_URL,
VUE_APP_UPLOAD_API_PREFIX: UPLOAD_API_PREFIX,
VUE_APP_VERTIFY_PROXY_URL: VERTIFY_PROXY_URL,
VUE_APP_VERTIFY_API_PREFIX: VERTIFY_API_PREFIX
} = process.env
// SERVICE_PROXY_URL 請求路徑
export const $BASE_URL = '/' + SERVICE_PROXY_URL + '/' + SERVICE_API_PREFIX
export const $VERTIFY_URL = '/' + VERTIFY_PROXY_URL + '/' + VERTIFY_API_PREFIX
export const $UPLOAD_URL = '/' + UPLOAD_PROXY_URL + '/'
export const httpRequest = createAxios({
config: {baseURL: $BASE_URL},
interceptors,
requestMethods: ['get', 'post', 'put', 'delete', 'upload']
})
export const httpVertify = createAxios({
config: {baseURL: $VERTIFY_URL},
interceptors,
requestMethods: ['get']
})
export const httpUpload = createAxios({
config: {baseURL: $UPLOAD_URL},
interceptors,
requestMethods: ['upload']
})
export default {
httpRequest, httpVertify, httpUpload
}
(5) vue.config.js和.env檔案配置
.env
## 這裡是配置環境變數,因為可能有多個代理,所以進行約束 => 括號內表示你的代理地址 名稱
## VUE_APP_[]_URL 代理目標地址
## VUE_APP_[]_PROXY_URL 代理使用 識別符號
## VUE_APP_[]__API_PREFIX 代理真實API介面的字首 識別符號,預設為空
## 舉例
VUE_APP_SERVICE_URL=http://localhost:3000
## 代理伺服器地址路徑
VUE_APP_SERVICE_PROXY_URL=BLOG_API
## 請求字首
VUE_APP_SERVICE_API_PREFIX=api/v2.0/
## 簡書身份認證
## 真實地址
VUE_APP_VERTIFY_URL=https://www.jianshu.com
## 代理伺服器地址值路徑
VUE_APP_VERTIFY_PROXY_URL=VERTIFY
VUE_APP_VERTIFY_API_PREFIX=
## 上傳檔案到簡書的地址
## 真實地址
VUE_APP_UPLOAD_URL=https://upload.qiniup.com
## 代理伺服器地址值路徑
VUE_APP_UPLOAD_PROXY_URL=UPLOAD
VUE_APP_UPLOAD_API_PREFIX=
## 轉發請求,安全獲取簡書的圖片
## 真實路徑
VUE_APP_JIANSHU_IMAGE_URL=https://upload-images.jianshu.io
## 代理欄位
VUE_APP_JIANSHU_IMAGE_PROXY_URL=GET_JIAN_SHU_IMAGE
VUE_APP_JIANSHU_IMAGE_API_PREFIX=
vue.config.js
const path = require('path')
function resolve(dir) {
return path.join(__dirname, dir)
}
//自動處理多個代理路徑
const proxyArr = ['SERVICE','UPLOAD','JIANSHU_IMAGE', 'VERTIFY']
const proxy = {} //代理物件
proxyArr.forEach((proxyname)=>{
const targetUrl= process.env['VUE_APP_' + proxyname + '_URL']
const targetProxyUrl= process.env['VUE_APP_' + proxyname + '_PROXY_URL']
proxy['/' + targetProxyUrl] = {
target: targetUrl,
secure: false,
changeOrigin: true,
pathRewrite: {
['^/'+targetProxyUrl]: ''
}
}
})
module.exports = {
...config,
devServer: {
port: '3000',
proxy
}
}