在大型前端項目中,當有不少接口實現數據輸入、流出並附加攔截,結合狀態管理,抵禦XSRF攻擊等時,統一管理API接口就成爲大型前端項目必須面對的環節。axios做爲最流行的基於Promise的HTTP庫能夠同時運行在瀏覽器端和服務器端,已經成爲大部分前端項目的首選。前端
經過JSON.stringify
咱們一樣能夠實現序列化,可是對於複雜ObjectJSON.stringify
的支持行不如qs.stringify
。因此經過引入qs這個庫,qs能夠幫咱們對深層嵌套的JSON以及Array形式進行序列化,讓咱們的API封裝兼容更多的場景。vue
var a = {name:'hehe',age:10}; qs.stringify(a) // 'name=hehe&age=10' JSON.stringify(a) // '{"name":"hehe","age":10}' 複製代碼
例外:如今後臺工程大多能夠在body裏面獲取json,array等,某些狀況下,可能後臺是直接讀取的字符串信息,這種狀況下,qs.stringify封裝參數中的JSON以及Array格式沒法獲取,須要使用JSON.stringify
去處理ios
在正式進行axios二次封裝以前,簡單瞭解一下axios對於配置項的處理;能夠從axios暴露出來的方法瞭解,能夠在axios.defaults上配置config,也能夠在攔截器上以及新的instance config上去配置;經過閱讀源碼,發現其實axios的config配置是經過merge方法去實現的:nginx
axios.create = function create(instanceConfig) { return createInstance(mergeConfig(axios.defaults, instanceConfig)); }; 複製代碼
axios default以及新的instance的config以外,也提供了初始化的默認config;vuex
var DEFAULT_CONTENT_TYPE = { 'Content-Type': 'application/x-www-form-urlencoded' }; function setContentTypeIfUnset(headers, value) { if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) { headers['Content-Type'] = value; } } 複製代碼
axios的config是這樣一個邏輯:element-ui
1.默認的初始化config與defaultconfig進行mergejson
2.將第一步獲得的結果和新instance上的config進行mergeaxios
經過分析,咱們能夠直接將請求的接口進行配置化處理,更方便的一步化適應各類場景後端
axios給我提供了一個default系列的屬性,能夠直接向axios.default
的一些屬性賦值,這個axios.default
的賦值會做用給所用axios請求;官方文檔給我提供了一些參考:好比設置默認的baseURL,爲基於token的請求把token放到header的Authorization中,以及設置post的請求類型;api
axios.defaults.baseURL = 'https://api.example.com'; axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'; 複製代碼
做爲一個出色的http請求庫,axios提供了強大請求攔截和響應攔截功能。
//引入vuex import store from '@/store' ... axios.interceptors.request.use(config => { //將token添加到了request的header裏面 const token = store.state.token; config.headers.common['Authorization'] = token // loading return config }, error => { console.log(error) return Promise.reject(error) }) 複製代碼
經過攔截器能夠實現請求的前置操做,例如,這裏實現了比較常見的將token添加到header中。固然,在default中處理token也是能夠的。因此網上大部分對攔截器的操做都是能夠放到defaults中執行的,並無什麼區別;我的認爲請求前攔截能夠結合一些定時器已經前端監控相關插件的使用。
須要注意一下響應攔截的執行順序,先執行axios.interceptors.response.use
而後再執行正常的響應處理;
// 響應攔截器 axios.interceptors.response.use( response => { // 這裏的response返回的HTTP狀態碼爲2XX的狀況,能夠在這裏集中處理200+JSON形式中JSON中先後端約定的狀態碼 }, //這裏的error返回的是HTTP狀態碼不是2XX的狀況,能夠在這裏處理不一樣HTTP的status error => { if (error.response.status) { switch (error.response.status) { case 401: //未登陸的處理 case 403: //權限不足的處理 break; case 404: // 404請求不存在的處理 break; // 其餘錯誤,直接拋出錯誤提示 default: //默認處理 } return Promise.reject(error.response); } } }); 複製代碼
經過上面對axios的具體config分析,咱們能夠經過增長merge結合封裝方法來實現多種場景的配置;能夠實現諸如,是否跨域攜帶cookie,是否附帶loading,配置特殊接口的請求時常等待等
能夠建立一個config.js
const configMap = { defaultConfig: { withCredentials: false, baseURL: path.baseUrl, headers: { post: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' }, }, }, long: { timeout: 60000, }, nocookie: { withCredentials: false, }, ... 複製代碼
經過在httpjs中引入
import configMap from './config' import { showFullScreenLoading, tryHideFullScreenLoading } from './loading' import merge from 'lodash.merge' ... merge(axios.defaults, configMap['defaultConfig']) function handleTypeString(type) { type.toLowerCase().split('-').map(item => merge(axios.defaults, configMap[item])) } export default { post(url, data, type) { handleTypeString(type) return axios({ method: 'post', 複製代碼
這樣能夠實現多個接口請求的配置組合,後面的會覆蓋前面的,一個接一個,實現axios請求的徹底配置化處理;
在vue中使用
import http from "@/api/http"; import path from "@/api/path"; //配合async await更加優雅 async test() { const res = await http.post(path.test, params, "long-nocookie"); 複製代碼
有些人比較喜歡使用偏函數的方式再包裝一層,也能夠再增長封裝一層使調用時候直接使用。
test(param) 複製代碼
封裝loading.js來處理部分url請求接口須要loading菊花圖的狀況;須要設置needLoadingCount來記錄處理多個須要loading請求接口處理的狀況。
import { Loading } from 'element-ui'; let loading; function startLoading() { // 使用Element loading.tart 方法 loading = Loading.service({ lock: true, text: 'loading……', background: 'rgba(0, 0, 0, 0.5)', }); } function endLoading() { // 使用Element loading.close 方法 loading.close(); } //經過needLoadingCount來記錄,在多個地方使用loading時候處理 let needLoadingCount = 0; export function showLoading() { if (needLoadingCount === 0) { startLoading(); } needLoadingCount++;//eslint-disable-line } export function tryHideLoading() { if (needLoadingCount >= 0) needLoadingCount--;//eslint-disable-line if (needLoadingCount === 0) { endLoading(); } } 複製代碼
在http中配置
if (type === 'long') { showFullScreenLoading() return axios(config).then(response=>{ tryHideFullScreenLoading() rerurn response; }) } //其餘無loading的axios請求 return axios(config) 複製代碼
在先後端分離的spa場景下,axios的baseUrl等各類環境參數是預先設定好的;而後打包成靜態文件,上傳到nginx或者tomcat相似的http服務器中,從本地開發到測試,提供靜態文件給不一樣的後臺去使用,可能不一樣後臺設置的接口地址是不同的,爲了不一個個的去打包,咱們須要配置一個針對不一樣域名環境的封裝;
import merge from 'lodash.merge' const path = { baseUrl: 'http://localhost:3000', login: '/users/login', test: '/test', }; const pathMap = { 'http://localhost:3001': { baseUrl: 'http://localhost:3001' }, 'http://localhost:3002': { baseUrl: 'http://localhost:3002',login:'/login' }, } const getClientIdByLocation = () => { const { href } = window.location; const matchedKey = Object.keys(pathMap).filter(url => href.indexOf(url) > -1); merge(path, pathMap[matchedKey]) }; getClientIdByLocation(); export default path; 複製代碼
能夠經過直接在pathMap中配置不一樣的url對應的path對象,來實現處理不一樣的url對應的baseUrl以及各類子路由狀況不一樣時候的狀況