前端緩存
提示
這里講的前端緩存是指前端對接口數(shù)據(jù)的緩存處理,而不是通過 HTTP(s)緩存
前言
通常會在項目中有這么些情況發(fā)生,比如每次頁面切換的時候都會請求接口,如果頻繁切換,也就會導(dǎo)致接口頻繁的請求,而且在數(shù)據(jù)基本沒有什么變動的情況下,這樣的做法明顯是浪費網(wǎng)絡(luò)資源的。所以我們出于考慮,要實現(xiàn)接口的緩存,避免頻繁的去請求接口。如果后端同學(xué)不給于幫助的話。。。那我們就進入今天的主題--前端緩存。(當然,能 http 緩存就 http 緩存最好了~)
怎么做?
思路
這里我們使用axios進行接口的請求,我們要用到axios的兩個功能--攔截器和cancleToken。首先我們要使用攔截器,去攔截要發(fā)送的請求,然后對比我們本地緩存池,看是否有在緩存池中存在,如果存在,則使用cancleToken直接取消請求,并從緩存池從返回數(shù)據(jù),如果不存在則正常請求,并在響應(yīng)攔截器中將該條請求存入緩存池中。當然,我們還需要一個過期時間,如果過期,則重新請求,更新緩存池的數(shù)據(jù),避免一直在緩存池中取老數(shù)據(jù)。
流程
具體流程圖入下:

提示
上圖右側(cè)的倆響應(yīng)攔截器其實同屬一個攔截器,這里為了區(qū)分,所以拆分成倆。
實現(xiàn)
import axios from 'axios'
// 定義一個緩存池用來緩存數(shù)據(jù)
let cache = {}
const EXPIRE_TIME = 60000
// 利用axios的cancelToken來取消請求
const CancelToken = axios.CancelToken
// 請求攔截器中用于判斷數(shù)據(jù)是否存在以及過期 未過期直接返回
axios.interceptors.request.use(config => {
// 如果需要緩存--考慮到并不是所有接口都需要緩存的情況
if (config.cache) {
let source = CancelToken.source()
config.cancelToken = source.token
// 去緩存池獲取緩存數(shù)據(jù)
let data = cache[config.url]
// 獲取當前時間戳
let expire_time = getExpireTime()
// 判斷緩存池中是否存在已有數(shù)據(jù) 存在的話 再判斷是否過期
// 未過期 source.cancel會取消當前的請求 并將內(nèi)容返回到攔截器的err中
if (data && expire_time - data.expire < EXPIRE_TIME) {
source.cancel(data)
}
}
return config
})
// 響應(yīng)攔截器中用于緩存數(shù)據(jù) 如果數(shù)據(jù)沒有緩存的話
axios.interceptors.response.use(
response => {
// 只緩存get請求
if (response.config.method === 'get' && response.config.cache) {
// 緩存數(shù)據(jù) 并將當前時間存入 方便之后判斷是否過期
let data = {
expire: getExpireTime(),
data: response.data
}
cache[`${response.config.url}`] = data
}
return response
},
error => {
// 請求攔截器中的source.cancel會將內(nèi)容發(fā)送到error中
// 通過axios.isCancel(error)來判斷是否返回有數(shù)據(jù) 有的話直接返回給用戶
if (axios.isCancel(error)) return Promise.resolve(error.message.data)
// 如果沒有的話 則是正常的接口錯誤 直接返回錯誤信息給用戶
return Promise.reject(error)
}
)
// 獲取當前時間
function getExpireTime() {
return new Date().getTime()
}
提示
之所以緩存邏輯寫在響應(yīng)攔截器中是因為只有在響應(yīng)攔截器中可以得到接口返回的數(shù)據(jù),而請求攔截器中,無法做到。
使用
然后頁面中接口請求如下配置:
<template>
<div>
i am page A
<router-link to="/">回首頁</router-link>
</div>
</template>
<script>
import axios from '../utils/axios'
export default {
mounted () {
// 加上屬性cache:true 則表示當前接口需要緩存(可以從緩存獲?。? axios('v2/book/1003078', {
cache: true
}).then(r => {
console.log(r)
})
}
}
</script>
或者在統(tǒng)一的api接口管理文件中配置:
import axios from './axios'
export const getBooks = () => {
return axios('v2/book/1003078', { cache: true })
}
簡單封裝
新建一個cache.js
// 緩存池
let CACHES = {}
export default class Cache {
constructor(axios) {
this.axios = axios
this.cancelToken = axios.CancelToken
this.options = {}
}
use(options) {
let defaults = {
expire: 60000, // 過期時間 默認一分鐘
storage: false, // 是否開啟緩存
storage_expire: 3600000, // 本地緩存過期時間 默認一小時
instance: this.axios, // axios的實例對象 默認指向當前axios
requestConfigFn: null, // 請求攔截的操作函數(shù) 參數(shù)為請求的config對象 返回一個Promise
responseConfigFn: null, // 響應(yīng)攔截的操作函數(shù) 參數(shù)為響應(yīng)數(shù)據(jù)的response對象 返回一個Promise
...options
}
this.options = defaults
this.init()
// if (options && !options.instance) return this.options.instance
}
init() {
// 初始化
let options = this.options
if (options.storage) {
// 如果開啟本地緩存 則設(shè)置一個過期時間 避免時間過久 緩存一直存在
this._storageExpire('expire').then(() => {
if (localStorage.length === 0) CACHES = {}
else mapStorage(localStorage, 'get')
})
}
this.request(options.requestConfigFn)
this.response(options.responseConfigFn)
}
request(cb) {
// 請求攔截器
let options = this.options
options.instance.interceptors.request.use(async config => {
// 判斷用戶是否返回 config 的 promise
let newConfig = cb && (await cb(config))
config = newConfig || config
if (config.cache) {
let source = this.cancelToken.source()
config.cancelToken = source.token
let data = CACHES[config.url]
let expire = getExpireTime()
// 判斷緩存數(shù)據(jù)是否存在 存在的話 是否過期 沒過期就返回
if (data && expire - data.expire < this.options.expire) {
source.cancel(data)
}
}
return config
})
}
response(cb) {
// 響應(yīng)攔截器
this.options.instance.interceptors.response.use(
async response => {
// 判斷用戶是否返回了 response 的 Promise
let newResponse = cb && (await cb(response))
response = newResponse || response
if (response.config.method === 'get' && response.config.cache) {
let data = {
expire: getExpireTime(),
data: response
}
CACHES[`${response.config.url}`] = data
if (this.options.storage) mapStorage(CACHES)
}
return response
},
error => {
// 返回緩存數(shù)據(jù)
if (this.axios.isCancel(error)) {
return Promise.resolve(error.message.data)
}
return Promise.reject(error)
}
)
}
// 本地緩存過期判斷
_storageExpire(cacheKey) {
return new Promise(resolve => {
let key = getStorage(cacheKey)
let date = getExpireTime()
if (key) {
// 緩存存在 判斷是否過期
let isExpire = date - key < this.options.storage_expire
// 如果過期 則重新設(shè)定過期時間 并清空緩存
if (!isExpire) {
removeStorage()
}
} else {
setStorage(cacheKey, date)
}
resolve()
})
}
}
/**
* caches: 緩存列表
* type: set->存 get->取
*/
function mapStorage(caches, type = 'set') {
Object.entries(caches).map(([key, cache]) => {
if (type === 'set') {
setStorage(key, cache)
} else {
// 正則太弱 只能簡單判斷是否是json字符串
let reg = /\{/g
if (reg.test(cache)) CACHES[key] = JSON.parse(cache)
else CACHES[key] = cache
}
})
}
// 清除本地緩存
function removeStorage() {
localStorage.clear()
}
// 設(shè)置緩存
function setStorage(key, cache) {
localStorage.setItem(key, JSON.stringify(cache))
}
// 獲取緩存
function getStorage(key) {
let data = localStorage.getItem(key)
return JSON.parse(data)
}
// 設(shè)置過期時間
function getExpireTime() {
return new Date().getTime()
}
然后在自定義的axios.js中引入
import axios from 'axios'
import Cache from './cache2'
// axios的自定義實例
let instance = axios.create({
baseURL: ''
})
let cache = new Cache(axios) // 將當前 axios 對象傳入 Cache 中
cache.use({
expire: 30000,
storage: true,
instance, // 如果有自定義axios實例 比如上面的instance 需要將其傳入instance 沒有則不傳
requestConfigFn: config => {
// 請求攔截自定義操作
if (config.header) {
config.header.token = 'i am token'
} else {
config.header = { token: 'i am token' }
}
// 需要將config對象通過 Promise 返回 cache 中 也可以使用new Promise的寫法
return Promise.resolve(config)
},
responseConfigFn: res => {
// 響應(yīng)攔截的自定義操作
if (!res.data.code) {
// 需要將 res 通過 Promise 返回
return Promise.resolve(res)
}
}
})
export default instance
發(fā)布NPM
該工具已經(jīng)打包至 NPM 庫,可通過包命令安裝:
# npm
npm install axios-request-cache --save
# yarn
yarn add axios-request-cache
總結(jié)
-
cache.js可能還有些情況未考慮進去 -
requestConfigFn和responseConfigFn能操作的空間可能也不夠大
后續(xù)還會繼續(xù)優(yōu)化