前端緩存
提示
這里講的前端緩存是指前端對接口數(shù)據(jù)的緩存處理累澡,而不是通過 HTTP(s)緩存
前言
通常會在項目中有這么些情況發(fā)生涉馅,比如每次頁面切換的時候都會請求接口,如果頻繁切換阱穗,也就會導致接口頻繁的請求俘种,而且在數(shù)據(jù)基本沒有什么變動的情況下,這樣的做法明顯是浪費網(wǎng)絡資源的篇亭。所以我們出于考慮缠捌,要實現(xiàn)接口的緩存,避免頻繁的去請求接口译蒂。如果后端同學不給于幫助的話曼月。。柔昼。那我們就進入今天的主題--前端緩存
哑芹。(當然,能 http 緩存就 http 緩存最好了~)
怎么做?
思路
這里我們使用axios
進行接口的請求捕透,我們要用到axios
的兩個功能--攔截器
和cancleToken
聪姿。首先我們要使用攔截器碴萧,去攔截要發(fā)送的請求,然后對比我們本地緩存池末购,看是否有在緩存池中存在破喻,如果存在,則使用cancleToken
直接取消請求盟榴,并從緩存池從返回數(shù)據(jù)曹质,如果不存在則正常請求,并在響應攔截器中將該條請求存入緩存池中曹货。當然咆繁,我們還需要一個過期時間,如果過期顶籽,則重新請求玩般,更新緩存池的數(shù)據(jù),避免一直在緩存池中取老數(shù)據(jù)礼饱。
流程
具體流程圖入下:
提示
上圖右側的倆響應攔截器其實同屬一個攔截器坏为,這里為了區(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會取消當前的請求 并將內容返回到攔截器的err中
if (data && expire_time - data.expire < EXPIRE_TIME) {
source.cancel(data)
}
}
return config
})
// 響應攔截器中用于緩存數(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會將內容發(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()
}
提示
之所以緩存邏輯寫在響應攔截器中是因為只有在響應攔截器中可以得到接口返回的數(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, // 響應攔截的操作函數(shù) 參數(shù)為響應數(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) {
// 如果開啟本地緩存 則設置一個過期時間 避免時間過久 緩存一直存在
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) {
// 響應攔截器
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
// 如果過期 則重新設定過期時間 并清空緩存
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()
}
// 設置緩存
function setStorage(key, cache) {
localStorage.setItem(key, JSON.stringify(cache))
}
// 獲取緩存
function getStorage(key) {
let data = localStorage.getItem(key)
return JSON.parse(data)
}
// 設置過期時間
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 => {
// 響應攔截的自定義操作
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
總結
-
cache.js
可能還有些情況未考慮進去 -
requestConfigFn
和responseConfigFn
能操作的空間可能也不夠大
后續(xù)還會繼續(xù)優(yōu)化