Фронтенд-инжиниринг (3): элегантно спроектируйте схему запроса на основе Axios в проекте.

axios

фактическиaxiosБыло предоставлено много мощных API, и мы можем вызывать их непосредственно в реальном использовании, но каждая команда и каждый проект вызывают аксиомы по-разному, особенно в крупномасштабном командном проекте, где логика взаимодействия с бэкендом сильно отличается. конфигурация сложна, а адресов много, поэтому необходима схема запроса с единым стилем, гибкой конфигурацией и централизованным управлением.

Первый шаг, управление интерфейсом

Сначала создайте проект с именем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Разработан механизм кэширования на основе адреса запросаurlи параметры запросаparamsКэшируйте каждый результат запроса, и вы можете установить лимит кеша и режим кеша для каждого результата запроса:

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 => {
            let newError = callback && (await callback(newError))
            error = newError || 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, браузер будет напрямую читать кеш и больше не будет отправлять запрос. В случае истечения срока действия или изменения адреса запроса или изменения параметров запроса браузер минует кеш и отправит запрос напрямую. (Сценарии, поддерживающие кэш подкачки)

Шаг 3: Настройте 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(config => {
    // Do something before request is sent
    return config;
}, error => {
    // Do something with request error
    return Promise.reject(error);
});

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

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,默认是json
    }
    let config = {...defaultConfig, ...extend}
    return axios(config).then(res => {
        // 可以统一返回的数据格式
        return 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 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.

наконец

Вся разработанная схема завершена:

Конечно, со сложностью проекта, в этом решении еще много областей, которые можно оптимизировать, например, глобальная загрузка, потому что лично я считаю, что оно подходит для мобильных терминалов и не подходит для ПК-терминалов, поэтому я не буду приведите примеры здесь шаг для инкапсуляции. Например, глобальную конфигурацию можно дополнить на третьем шаге.

Этот план не самый лучший, но это самый элегантный способ, который я подытожил на данный момент.Я также приветствую ваши ценные мнения.