在Vue中應該如何封裝Axios 管理API介面
之前專案一直是api介面直接寫在專案中的,非常亂,不易統一維護和管理,特意學習了其它前輩如何管理api,儲存下來。
一、axios的封裝
Vue\React中多使用axios庫做資料請求,如果還對axios不瞭解的,可以移步axios文件。
安裝
npm install axios; // 安裝axios
引入
一般我會在專案的src目錄中,新建一個request資料夾,然後在裡面新建一個http.js和一個api.js檔案。http.js檔案用來封裝我們的axios,api.js用來統一管理我們的介面。
// 在http.js中引入axios import axios from 'axios'; // 引入axios import QS from 'qs'; // 引入qs模組,用來序列化post型別的資料,後面會提到 // vant的toast提示框元件,大家可根據自己的ui元件更改。 import { Toast } from 'vant';
環境的切換
我們的專案環境可能有開發環境、測試環境和生產環境。我們通過node的環境變數來匹配我們的預設的介面url字首。axios.defaults.baseURL可以設定axios的預設請求地址就不多說了。
// 環境的切換 if (process.env.NODE_ENV == 'development') { axios.defaults.baseURL = 'https://www.baidu.com';} else if (process.env.NODE_ENV == 'debug') { axios.defaults.baseURL = 'https://www.ceshi.com'; } else if (process.env.NODE_ENV == 'production') { axios.defaults.baseURL = 'https://www.production.com'; }
設定請求超時
通過axios.defaults.timeout設定預設的請求超時時間。例如超過了10s,就會告知使用者當前請求超時,請重新整理等。
axios.defaults.timeout = 10000;
post請求頭的設定
post請求的時候,我們需要加上一個請求頭,所以可以在這裡進行一個預設的設定,即設定post的請求頭為application/x-www-form-urlencoded;charset=UTF-8
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
請求攔截
我們在傳送請求前可以進行一個請求的攔截,為什麼要攔截呢,我們攔截請求是用來做什麼的呢?比如,有些請求是需要使用者登入之後才能訪問的,或者post請求的時候,我們需要序列化我們提交的資料。這時候,我們可以在請求被髮送之前進行一個攔截,從而進行我們想要的操作。
請求攔截
// 先匯入vuex,因為我們要使用到裡面的狀態物件
// vuex的路徑根據自己的路徑去寫
import store from '@/store/index';
// 請求攔截器axios.interceptors.request.use(
config => {
// 每次傳送請求之前判斷vuex中是否存在token
// 如果存在,則統一在http請求的header都加上token,這樣後臺根據token判斷你的登入情況
// 即使本地存在token,也有可能token是過期的,所以在響應攔截器中要對返回狀態進行判斷
const token = store.state.token;
token && (config.headers.Authorization = token);
return config;
},
error => {
return Promise.error(error);
})
響應的攔截
// 響應攔截器
axios.interceptors.response.use(
response => {
// 如果返回的狀態碼為200,說明介面請求成功,可以正常拿到資料
// 否則的話丟擲錯誤
if (response.status === 200) {
return Promise.resolve(response);
} else {
return Promise.reject(response);
}
},
// 伺服器狀態碼不是2開頭的的情況
// 這裡可以跟你們的後臺開發人員協商好統一的錯誤狀態碼
// 然後根據返回的狀態碼進行一些操作,例如登入過期提示,錯誤提示等等
// 下面列舉幾個常見的操作,其他需求可自行擴充套件
error => {
if (error.response.status) {
switch (error.response.status) {
// 401: 未登入
// 未登入則跳轉登入頁面,並攜帶當前頁面的路徑
// 在登入成功後返回當前頁面,這一步需要在登入頁操作。
case 401:
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
});
break;
// 403 token過期
// 登入過期對使用者進行提示
// 清除本地token和清空vuex中token物件
// 跳轉登入頁面
case 403:
Toast({
message: '登入過期,請重新登入',
duration: 1000,
forbidClick: true
});
// 清除token
localStorage.removeItem('token');
store.commit('loginSuccess', null);
// 跳轉登入頁面,並將要瀏覽的頁面fullPath傳過去,登入成功後跳轉需要訪問的頁面
setTimeout(() => {
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
});
}, 1000);
break;
// 404請求不存在
case 404:
Toast({
message: '網路請求不存在',
duration: 1500,
forbidClick: true
});
break;
// 其他錯誤,直接丟擲錯誤提示
default:
Toast({
message: error.response.data.message,
duration: 1500,
forbidClick: true
});
}
return Promise.reject(error.response);
}
}
});
響應攔截器很好理解,就是伺服器返回給我們的資料,我們在拿到之前可以對他進行一些處理。例如上面的思想:如果後臺返回的狀態碼是200,則正常返回資料,否則的根據錯誤的狀態碼型別進行一些我們需要的錯誤,其實這裡主要就是進行了錯誤的統一處理和沒登入或登入過期後調整登入頁的一個操作。
要注意的是,上面的Toast()方法,是我引入的vant庫中的toast輕提示元件,你根據你的ui庫,對應使用你的一個提示元件。
封裝get方法和post方法
我們常用的ajax請求方法有get、post、put等方法,相信小夥伴都不會陌生。axios對應的也有很多類似的方法,不清楚的可以看下文件。但是為了簡化我們的程式碼,我們還是要對其進行一個簡單的封裝。下面我們主要封裝兩個方法:get和post。
get方法:我們通過定義一個get函式,get函式有兩個引數,第一個引數表示我們要請求的url地址,第二個引數是我們要攜帶的請求引數。get函式返回一個promise物件,當axios其請求成功時resolve伺服器返回 值,請求失敗時reject錯誤值。最後通過export丟擲get函式。
/**
* get方法,對應get請求
* @param {String} url [請求的url地址]
* @param {Object} params [請求時攜帶的引數]
*/
export function get(url, params){
return new Promise((resolve, reject) =>{
axios.get(url, {
params: params
}).then(res => {
resolve(res.data);
}).catch(err =>{
reject(err.data)
})
});}
post方法:原理同get基本一樣,但是要注意的是,post方法必須要使用對提交從引數物件進行序列化的操作,所以這裡我們通過node的qs模組來序列化我們的引數。這個很重要,如果沒有序列化操作,後臺是拿不到你提交的資料的。這就是文章開頭我們import QS from 'qs';
的原因。如果不明白序列化是什麼意思的,就百度一下吧,答案一大堆。
/**
* post方法,對應post請求
* @param {String} url [請求的url地址]
* @param {Object} params [請求時攜帶的引數]
*/
export function post(url, params) {
return new Promise((resolve, reject) => {
axios.post(url, QS.stringify(params))
.then(res => {
resolve(res.data);
})
.catch(err =>{
reject(err.data)
})
});
}
這裡有個小細節說下,axios.get()
方法和axios.post()
在提交資料時引數的書寫方式還是有區別的。區別就是,get的第二個引數是一個{},然後這個物件的params屬性值是一個引數物件的。而post的第二個引數就是一個引數物件。兩者略微的區別要留意哦!
api統一管理
整齊的api就像電路板一樣,即使再複雜也能很清晰整個線路。上面說了,我們會新建一個api.js,然後在這個檔案中存放我們所有的api介面。考慮到模組化、多人開發、處理介面域名有多個情況等情況。
這裡這裡呢新建了一個api資料夾,裡面有一個index.js和一個baseUrl.js,以及多個根據模組劃分的介面js檔案。index.js是一個api的出口,baseUrl.js管理介面域名,其他js則用來管理各個模組的介面。
先放index.js程式碼:
// api/index.js
/**
* api介面的統一出口
*/
// 文章模組介面
import article from "./article";
// 登入模組介面
import login from "./login";
// user模組介面
import user from "./user";
// message模組介面
import message from "./message";
// 匯出介面
export default {
article,
login,
user,
message
};
index.js是一個api介面的出口,在元件裡呼叫api的時候引入這個檔案即可,這樣就可以把api介面根據功能劃分為多個模組,利於多人協作開發,比如一個人只負責一個模組的開發等,還能方便每個模組中介面的命名哦。
baseUrl.js:
// api/baseUrl.js
/**
* 介面域名的管理
*/
const base = {
// 測試介面
dev: "https://www.xxxx1.com/api",
bd: "http://www.xxxxx2.com/api"
};
export default base;
通過base.js來管理我們的介面域名,不管有多少個都可以通過這裡進行介面的定義。即使修改起來,也是很方便的。
最後就是介面模組的說明,例如上面的article.js:
/**
* article模組介面列表
*/
import base from "./baseUrl"; // 匯入介面域名列表
import axios from "./http"; // 匯入http中建立的axios例項
const article = {
// 獲取新聞列表資料
articleList(params) {
return axios.get(`${base.dev}/topics`, {
params: params
});
},
// 獲取新聞詳情
articleDetail(id) {
return axios.get(`${base.dev}/topic/${id}`);
}
};
export default article;
- 通過直接引入我們封裝好的axios例項,然後定義介面、呼叫axios例項並返回,可以更靈活的使用axios,比如你可以對post請求時提交的資料進行一個qs序列化的處理等。
- 請求的配置更靈活,你可以針對某個需求進行一個不同的配置。關於配置的優先順序,axios文件說的很清楚,這個順序是:在
lib/defaults.js
找到的庫的預設值,然後是例項的defaults
屬性,最後是請求的config
引數。後者將優先於前者。 - restful風格的介面,也可以通過這種方式靈活的設定api介面地址。
其他的api介面,就在api.js中繼續往下面擴充套件就可以了。友情提示,為每個介面寫好註釋哦!!!
api介面管理的一個好處就是,我們把api統一集中起來,如果後期需要修改介面,我們就直接在api.js中找到對應的修改就好了,而不用去每一個頁面查詢我們的介面然後再修改會很麻煩。關鍵是,萬一修改的量比較大,就規格gg了。還有就是如果直接在我們的業務程式碼修改介面,一不小心還容易動到我們的業務程式碼造成不必要的麻煩。
最後,為了方便api的呼叫,我們需要將其掛載到vue的原型上。在main.js中:
import Vue from 'vue'
import App from './App'
import router from './router' // 匯入路由檔案
import store from './store' // 匯入vuex檔案
import api from './api' // 匯入api介面
Vue.prototype.$api = api; // 將api掛載到vue的原型上
然後我們可以在頁面中這樣呼叫介面,eg:
methods: {
onLoad(id) {
this.$api.article.articleDetail(id, {
api: 123
}).then(res=> {
// 執行某些操作
})
}
}
好了,最後把http.js中axios封裝的優化程式碼奉上。
// http.js
/**
* axios封裝
* 請求攔截、響應攔截、錯誤統一處理
*/
import axios from 'axios';
// 用來序列化post型別的資料
import QS from 'qs';
import router from '../router';
import store from '../store/index';
import { Toast } from 'vant';
/**
* 提示函式
* 禁止點選蒙層、顯示一秒後關閉
*/
const tip = msg => {
Toast({
message: msg,
duration: 1000,
forbidClick: true
});
}
/**
* 跳轉登入頁
* 攜帶當前頁面路由,以期在登入頁面完成登入後返回當前頁面
*/
const toLogin = () => {
router.replace({
path: '/login',
query: {
redirect: router.currentRoute.fullPath
}
});
}
/**
* 請求失敗後的錯誤統一處理
* @param {Number} status 請求失敗的狀態碼
*/
const errorHandle = (status, other) => {
// 狀態碼判斷
switch (status) {
// 401: 未登入狀態,跳轉登入頁
case 401:
toLogin();
break;
// 403 token過期
// 清除token並跳轉登入頁
case 403:
tip('登入過期,請重新登入');
localStorage.removeItem('token');
store.commit('loginSuccess', null);
setTimeout(() => {
toLogin();
}, 1000);
break;
// 404請求不存在
case 404:
tip('請求的資源不存在');
break;
default:
console.log(other);
}}
// 建立axios例項
const service = axios.create({
// 設定超時時間,單位毫秒
timeout: 12 * 1000
});
// 設定post請求頭
service.defaults.headers.post['Content-Type'] =
'application/x-www-form-urlencoded;charset=UTF-8';
// 請求重試次數
service.defaults.retry = 3;
// 請求重試時間間隔,單位毫秒
service.defaults.retryDelay = 1000;
// 是否重試
service.defaults.shouldRetry = true;
/**
* 請求攔截器
* 每次請求前,如果存在token則在請求頭中攜帶token
*/
service.interceptors.request.use(
config => {
// 登入流程控制中,根據本地是否存在token判斷使用者的登入情況
// 但是即使token存在,也有可能token是過期的,所以在每次的請求頭中攜帶token
// 後臺根據攜帶的token判斷使用者的登入情況,並返回給我們對應的狀態碼
// 而後我們可以在響應攔截器中,根據狀態碼進行一些統一的操作。
const token = store.state.token;
token && (config.headers.Authorization = token);
return config;
},
error => Promise.error(error))
// 響應攔截器
service.interceptors.response.use(
// 請求成功
res => res.status === 200 ? Promise.resolve(res) : Promise.reject(res),
// 請求失敗
error => {
const { response } = error;
if (response) {
// 請求已發出,但是不在2xx的範圍
errorHandle(response.status, response.data.message);
return Promise.reject(response);
} else {
// 處理斷網的情況
// eg:請求超時或斷網時,更新state的network狀態
// network狀態在app.vue中控制著一個全域性的斷網提示元件的顯示隱藏
// 關於斷網元件中的重新整理重新獲取資料,會在斷網元件中說明
if (!window.navigator.onLine) {
store.commit('changeNetwork', false);
} else {
return Promise.reject(error);
}
}
});
export default service;
完整目錄結構大概是這樣的:
├── src // 生產目錄
│ ├── assets // css js 和圖片資源
│ └── api // axio請求資料目錄
│ ├── baseUrl.js // 管理介面域名檔案
│ ├── http.js // 封裝axios檔案
│ ├── index.js // api介面的統一出口
│ ├── article.js // article模組介面列表檔案
│ └── ... // 其它模組介面列表檔案