前端工程化(3):在項目中優雅的設計基於Axios的請求方案

其實axios已經提供了不少很強大的api,咱們在實際使用中直接調用就能夠,可是每一個團隊每一個項目調用axios的姿式不同,特別是在一個大型的團隊項目中,與後端交互邏輯迥異、配置複雜、地址繁多,因此一個風格統一化、配置靈活化、管理集中化的請求方案必不可少。javascript

第一步、接口管理

首先在項目中建立一個名爲api的文件夾,用來統一管理與後臺交互的邏輯。java

分類

api文件夾下分別建立文件夾,按類型對接口地址進行分類(這點頗有必要,特別是在大型項目中,按類型進行分類能讓你快速定位到接口寫入的位置):ios

在每一個文件夾下再建立 index.js文件,寫入屬於這個類型的全部接口地址:

cmsgit

...,
export const CMS_DATA = '/cms/renderData'
複製代碼

membergithub

...,
export const MEMBER_INFO = '/rights/memberInfo'
複製代碼

拋出

api文件夾下建立index.js文件:json

把全部類型的接口統一暴露出去:axios

// cms信息
export * from './cms'
// 會員信息
export * from './member'
複製代碼

第二步、緩存機制

api文件夾下建立cache.js文件:後端

基於axios開發了一套緩存機制,基於請求地址url和請求參數params給每一個請求結果進行緩存,同時能夠給每一個請求結果設置緩存有限期和緩存模式:api

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(i)
                if (!this.caches.includes(key) && key.includes('?cacheParams=')) {
                    window.localStorage.removeItem(key)
                }
            }
        }
    }
}

複製代碼

因爲緩存機制是基於url+params來進行緩存的,在有效期內再次訪問相同的url+params,瀏覽器就會直接讀取緩存不會再發送請求。若是有效期過了或者請求地址變了或者請求參數變了,瀏覽器則會繞過緩存直接發送請求。(支持分頁緩存的場景)瀏覽器

第三步、配置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
複製代碼

若是沒有采用緩存機制的話,能夠直接配置攔截器,以下:

axios.interceptors.request.use(function (config) {
    // Do something before request is sent
    return config;
}, function (error) {
    // Do something with request error
    return Promise.reject(error);
});

// Add a response interceptor
axios.interceptors.response.use(function (response) {
    // Do something with response data
    return response;
}, function (error) {
    // Do something with response error
    return Promise.reject(error);
});

export default axios
複製代碼

第四步、請求封裝

api文件夾下建立base.js文件:

主要封裝幾個經常使用的方法,這裏就列舉經常使用的getpost方法:

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,默認是json
    }
    let config = {...defaultConfig, ...extend}
    return axios(config).then(res => {
        // 能夠統一返回的數據格式
        return Promise.resolve(res)
    }, err => {
        return 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 => {
        // 能夠統一返回的數據格式
        return Promise.resolve(res)
    }, err => {
        return 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)
        }),
    }
}
複製代碼

緩存默認是關閉的,須要手動開啓,若是開啓的話,緩存有效期默認是10分鐘,緩存的模式默認爲sessionStorage

最後

整個設計好的方案就完成了:

固然,隨着項目的複雜度,這個方案還有不少能夠優化的地方,好比全局loading,由於我的感受適合移動端不適合pc端,因此在這就不舉例出來,有須要的同窗能夠在第四步進行封裝。也好比全局的配置,能夠在第三步進行補充。

這個方案說不上最好,但目前是我總結出來最優雅的方式了,也歡迎大佬們提出寶貴的意見。

相關文章
相關標籤/搜索