Vue 全局缓存 字典缓存 节流
在后台管理系统中基本上都会有字典之类的操作。这类操作有一个共性:相同接口、不同参数返回不同的目标数据、数据结构一致。使用上无非是一些下拉选项、多选、状态翻译等。有一个很实际的问题,A页面中存在某个下拉B页面也存在,一般的做法是两次请求,分别获取数据。但是当项目中字典数据应用越来越多的时候,大量重复的请求无法避免,造成资源浪费。实现方案为:当需要的时候进行缓存,再次使用的时候直接取数据,不用再次请求
前言
在后台管理系统中基本上都会有字典之类的操作。这类操作有一个共性:相同接口、不同参数返回不同的目标数据、数据结构一致。使用上无非是一些下拉选项、多选、状态翻译等。
有一个很实际的问题,A页面中存在某个下拉B页面也存在,一般的做法是两次请求,分别获取数据。但是当项目中字典数据应用越来越多的时候,大量重复的请求无法避免,造成资源浪费。
一般来说有两个解决办法:
1.随着项目启动将所有需要获取的数据获取一遍,缓存起来,用的时候直接取。优点是直观,方便管理。缺点是启动时的网络压力很大,用户可能不会点开很多页面,一定程度上资源浪费。
2.当需要的时候进行缓存,再次使用的时候直接取数据,不用再次请求。优点是灵活,按需就取。缺点是,依赖发布订阅模式(Vue中双向绑定),每次都需确认调用。
今天要说的是方法2的一种实现。
实现
直接上干货
import Vue from 'vue'
import Api from '@/api/index.js'
const cache = {
namespaced: true,
state: {
dict: {},
queue: []
},
mutations: {
SET_DICT: (state, { type, list }) => {
// 设置值:注意得用Vue.set方法
Vue.set(state.dict, type, list)
},
SET_QUEUE: (state, type) => {
state.queue.push(type)
},
DEL_QUEUE: (state, type) => {
// 找到并删除
for (let i = 0; i < state.queue.length; i++) {
const item = state.queue[i]
if (item === type) {
state.queue.splice(i, 1)
break
}
}
}
},
actions: {
// 缓存字典内容
dict({ commit, state }, type) {
// 判断是否已经缓存过,缓存过后将不再获取数据
const isExistence = state.queue.some((item) => {
return item === 'dict/' + type
})
// 没有缓存则请求数据
if (!isExistence) {
// 在异步请求之前将该任务标记为已缓存(存入队列)
commit('SET_QUEUE', 'dict/' + type)
return new Promise((resolve, reject) => {
return Api.getDict(type)
.then((res) => {
const { code, data } = res
if (code && code !== 200) {
// 获取失败移除缓存队列
commit('DEL_QUEUE', 'dict/' + type)
reject(res)
} else {
// 获取到的值存入字典
commit('SET_DICT', { type: type, list: data })
resolve(res)
}
})
.catch((error) => {
// 获取失败移除缓存队列
commit('DEL_QUEUE', 'dict/' + type)
reject(error)
})
})
} else {
return state.dict[type]
}
}
},
getters: {
getDict: (state) => {
return state.dict
},
getDictByType: (state) => (type) => {
/**
* 闭包特性。返回一个函数,该函数接收一个type参数。
* 同时由于闭包还持有state对象,故可以返回需要的值。
*/
return state.dict[type]
}
}
}
export default cache
其中Api对象具体实现由各位自己定义,以及getDict函数是假定的,回调内容也可以根据具体的架构进行调整。
解析
上述代码到底实现了什么呢?缓存与队列。
在异步编程中要考虑的是并发,如何才能避免同一数据多次请求的发生,是这个程序的一个重点。我采用了一个简单队列来避免,只要发起了就不再发起相同的请求,在成功时存入缓存,失败时清除队列里的数据。
这里还可以拓展一下,类似于失败后重试功能,思路大概是定时定次轮询,需要自行实现。
上述只是针对字典的缓存,但,只要符合相关共性的都可以集成到这个程序中,大同小异。
使用
实现一个与之相关的组件,字典下拉组件,采用的是Element UI。
<template>
<el-select v-model="dataValue" :placeholder="getPlaceholder"
style="width:100%" :disabled="disabled" :readonly="readonly" :size="size">
<el-option v-show="!isRequired" :label="getPlaceholder" value="">
</el-option>
<el-option v-for="item in cacheDict[code]" :key="item.dm" :label="item.dmmc"
:value="item.dm"></el-option>
</el-select>
</template>
<script>
/** *
* 字典字段过滤组件
*/
import { mapGetters } from 'vuex'
export default {
name: 'DictSelect',
components: {},
props: {
/**
* 双向绑定
*/
value: [String, Number, Boolean, Array, Object],
/**
* 字典编码
*/
code: {
type: String,
required: true
},
/**
* 描述
*/
placeholder: {
type: String,
default: undefined
},
/**
* 是否显示请选择
*/
isRequired: {
type: Boolean,
default: false
},
/**
* 是否禁用
*/
disabled: {
type: Boolean,
default: false
},
/**
* 是否只读
*/
readonly: {
type: Boolean,
default: false
},
/**
* 组件大小
*/
size: {
type: String,
default: 'small'
}
},
data() {
return {
dataValue: ''
}
},
computed: {
getPlaceholder() {
return this.placeholder != undefined &&
this.placeholder != 'undefined' &&
this.placeholder != ''
? '请选择' + this.placeholder
: '请选择'
},
...mapGetters({ cacheDict: 'cache/getDict' })
},
watch: {
value(val, oldValue) {
if (val !== this.dataValue) {
this.dataValue = val
}
},
dataValue(val, oldValue) {
if (val !== this.value) {
this.$emit('input', val)
}
}
},
created: function () {
this.dataValue = this.value
},
mounted: function () {
this.getData()
},
methods: {
async getData() {
await this.$store.dispatch('cache/dict', this.code)
}
}
}
</script>
结合上述缓存程序,我们可以发现,在字典下拉组件中的 getData() 虽然每渲染一次都调用了,但由于 缓存程序中 dict 函数实现里会检测是否已经存在,所以不管他有没有返回值,这里都只是作为一个资源确认方式。
使用了vue的双向绑定,所以我们无需关心数据什么时候会有,有了自然会渲染。
而在页面中使用起来就更加方便
<DictSelect v-model="formData.dictType" code="106"></DictSelect>
总结
核心问题是节流,引申出弱业务关联的抽象实现。本质上是一种思路,具体方案还得根据具体框架实现。
将问题分解,然后以叠金字塔的方式逐步实现,最后塔尖为我们所用。
更多推荐
所有评论(0)