前端工程化(3):在專案中優雅的設計基於Axios的請求方案
其實 axios 已經提供了很多很強大的api,我們在實際使用中直接呼叫就可以,但是每個團隊每個專案呼叫axios的姿勢不一樣,特別是在一個大型的團隊專案中,與後端互動邏輯不同、配置複雜、地址較多,所以一個統一化、功能全面、可集中管理的請求方案必不可少。
第一步、介面管理
首先在專案中建立一個資料夾命名為api,用來專門統一管理與後臺互動的邏輯。
分類
在api資料夾下分別建立資料夾,按型別對介面地址進行分類(這點很有必要,特別是在大型專案中,按型別進行分類能讓你快速定位到介面寫在哪):

index.js
檔案,寫入屬於這個型別的所有介面地址:
cms
..., export const CMS_DATA = '/cms/renderData' 複製程式碼
member
..., export const MEMBER_INFO = '/rights/memberInfo' 複製程式碼
丟擲
在api資料夾下建立 index.js
檔案:

把所有型別的介面統一暴露出去:
// cms資訊 export * from './cms' // 會員資訊 export * from './member' 複製程式碼
第二步、快取機制
在api資料夾下建立 cache.js
檔案:

基於axios開發了一套快取機制,可以給每個請求結果進行快取,並可以給每個請求結果設定快取有限時間和快取模式:
export default class Cache { constructor(axios, config = {}) { this.axios = axios this.caches = [] if (!this.axios) { throw new Error('請傳入axios例項') } this.config = config this.defaultConfig = { cache: false, expire: 100 * 1000 } this.CancelToken = this.axios.CancelToken this.init() } init() { this.requestInterceptor(this.config.requestInterceptorFn) this.responseInterceptor(this.config.responseInterceptorFn) window.onbeforeunload = () => { this.mapStorage() } } requestInterceptor(callback) { this.axios.interceptors.request.use(async config => { let newConfig = callback && (await callback(config)) config = newConfig || config let { url, data, params, cacheMode, cache = this.defaultConfig.cache, expire = this.defaultConfig.expire } = config if (cache === true) { let getKey = data ? `${url}?cacheParams=${data}` : `${url}?cacheParams=${params}` let obj = this.getStorage(cacheMode, getKey) // 判斷快取資料是否存在 if (obj) { let curTime = this.getExpireTime() let source = this.CancelToken.source() config.cancelToken = source.token // 判斷快取資料是否存在,存在的話是否過期,如果沒過期就停止請求返回快取 if (curTime - obj.expire < expire) { source.cancel(obj) } else { this.removeStorage(cacheMode, url) } } } else { this.clearStorage(url) } return config }, error => { return Promise.reject(error) }) } responseInterceptor(callback) { this.axios.interceptors.response.use(async response => { let newResponse = callback && (await callback(response)) response = newResponse || response // the http request error, do not store the result, direct return result if (response.status !== 200 || response.data.ret || !response.data.success) { return response.data } /* * `data` is the data to be sent as the request body, only applicable for request methods 'PUT', 'POST', and 'PATCH' * `params` are the URL parameters to be sent with the request, can be applicable for request methods 'GET' */ let { url, cache, cacheMode, data, params } = response.config if (cache === true) { let obj = { expire: this.getExpireTime(), params, data, result: response.data } let setKey = data ? `${url}?cacheParams=${data}` : `${url}?cacheParams=${params}` this.caches.push(setKey) this.setStorage(cacheMode, setKey, obj) } return response.data }, error => { // 返回快取資料 if (this.axios.isCancel(error)) { return Promise.resolve(error.message.result) } return Promise.reject(error) }) } // 設定快取 setStorage(mode = 'sessionStorage', key, cache) { window[mode].setItem(key, JSON.stringify(cache)) } // 獲取快取 getStorage(mode = 'sessionStorage', key) { let data = window[mode].getItem(key) return JSON.parse(data) } // 清除快取 removeStorage(mode = 'sessionStorage', key) { window[mode].removeItem(key) } // 設定過期時間 getExpireTime() { return new Date().getTime() } // 清空快取 clearStorage(key) { if (window.localStorage.getItem(key)) { window.localStorage.removeItem(key) } else { window.sessionStorage.removeItem(key) } } // 清空沒用到的快取 mapStorage() { let length = window.localStorage.length if (length) { for (let i = 0; i < length; i++) { let key = window.localStorage.key(length) if (!this.caches.includes(key) && key.includes('?cacheParams=')) { window.localStorage.removeItem(key) } } } } } 複製程式碼
快取預設是關閉的,如果開啟的話,快取有效期預設是10分鐘,快取的模式預設為sessionStorage。具體設定方式可以參考第六步舉例。
第三步、配置Axios
在api資料夾下建立 config.js
檔案,用來存放axios的一些預配置資訊:

全域性配置
import axios from 'axios' import Cache from './cache' axios.defaults.withCredentials = true axios.defaults.baseURL = process.env.NODE_ENV === 'production' ? '' : '/api' axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8' axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest' 複製程式碼
攔截器
由於設計的快取機制依賴攔截器機制,為了避免額外的配置攔截器,在設計快取機制時留有攔截器配置入口,如下:
new Cache(axios, { requestInterceptorFn: config => { // 自定義請求攔截器 /* */ // 需要用Promise將config返回 return Promise.resolve(config) }, responseInterceptorFn: response => { // 自定義響應攔截器,可統一返回的資料格式也可攔截錯誤 /* */ // 需要用Promise將response返回 return Promise.resolve(response) } }) export default axios 複製程式碼
第四步、請求封裝
在api資料夾下建立 base.js
檔案:

主要封裝幾個常用的方法,這裡就列舉常用的get和post方法:
import axios from './config' import qs from 'qs' export const post = (url, data, extend = {isJson: true, cache: false}) => { let defaultConfig = { url, method: 'POST', data: extend.isJson ? data : qs.stringify(data) // 通過isJson來確定傳參格式是json還是formData } let config = {...defaultConfig, ...extend} return axios(config).then(res => { Promise.resolve(res) // 可以給返回的資料剝衣服 }, err => { Promise.reject(err) }) } export const get = (url, data, extend = {cache: false}) => { let defaultConfig = { url, method: 'GET', params: data } let config = {...defaultConfig, ...extend} return axios(config).then(res => { Promise.resolve(res) // 可以給返回的資料剝衣服 }, err => { Promise.reject(err) }) } 複製程式碼
第五步、全域性註冊
在api資料夾下建立 install.js
檔案:

把封裝好的方法註冊到全域性:
import { get, post } from 'api/base' export const install = function(Vue, config = {}) { Vue.prototype.$_get = get Vue.prototype.$_post = post } 複製程式碼
在main.js中寫入:
import { install as Axios } from './api/install' Vue.use(Axios) 複製程式碼
第六步、呼叫
呼叫時只需要引入想要呼叫的介面地址就行:
import { CMS_DATA, MEMBER_INFO } from 'api' methods: { receiveCMS() { // post引數形式為formData this.$_post(CMS_DATA, data, { jsJson = false }).then(res => { console.log(res) }), }, receiveMember() { // 開啟快取,設定快取時間為一個小時,快取的模式為localStorage this.$_get(MEMBER_INFO, data, { cache = true, expires = 1000 * 60 * 60, cacheMode = 'localStorage' }).then(res => { console.log(res) }), } } 複製程式碼
當然隨著專案的複雜度,這個方案還有很多可以優化的地方,比如全域性loading,因為個人感覺適合移動端不適合pc端,所以在這就不舉例出來,有需要的同學可以在第四步進行封裝。也比如全域性的配置,可以在第三步進行補充。