前端数据字典
前序:刚满一年的不知名公司的前端CV仔的不重要看法
1. 什么是数据字典
2. 为什么要使用数据字典
需求来咯~~
后端大佬:有个操作符下拉框的数据是这样子>, <, =, !=, >=, <=,你加一下。kv一致,字段是opt。
此时一个看似聪明的前端CV仔,写出了以下代码
<el-select v-model="search.opt" style="width: 100px" placeholder="请选择操作符">
<el-option label=">" value=">" />
<el-option label="<" value="<" />
<el-option label="=" value="=" />
<el-option label="!=" value="!=" />
<el-option label=">=" value=">=" />
<el-option label="<=" value="<=" />
</el-select>
需求又来咯,后端:还有几个页面也要加这个操作符下拉,字段不变
CV仔:“可以,简单”,然后就去CV了,瞬间搞定。
需求又双来了,后端:!=的值要换成<>。
CV仔只能一个一个页面去修改,聪明点的CV仔使用全局替换。
此时某前端大佬B 登录看了下你的代码,摇摇头, “你这这么多的地方使用这个字典,这样子写维护多麻烦啊,你可以把他写到一个文件里统一维护”。
CV仔连忙点头,是啊,我抽空去写一下。实则内心,“**,我觉得很好啊,不用去改啊。”,但还是一阵jj查看,于是在src下新建了个dicts文件夹,并在index.js中写了如下内容:
export const DICT_OPTS = [{
label: '>',
value: '>',
{
label: '!=',
value: '<>'
},
....
}]
然后在使用该字典的文件内
.....
<el-option
v-for="item in DICT_OPT"
:key="item.value"
:label="item.label"
:value="item.value" />
.....
import { DICT_OPT } from '@/dicts'
此后要再修改这个字典就可以快速定位到这个文件,再进行修改了。
3. 字典实现方式
1. 本地
上方把所有字典写入前端文件进行维护的实现方式就是“本地存储”之一,你也可以将其存到localStorage/sessionStorage/vuex/pinia中,个人认为没有特殊需求不需要存储到storage和状态管理库中,因为本地存储,本身就已经把数据写在客户端中了,出了有什么我没想到的需求,欢迎补充。
注意点:
- 对于
localStorage和sessionStorage要注意存储大小啊,正常你随便用。 - 上线时候如果本地的dict.js有修改,要同步到正在使用的用户,尽管通常上线是在晚上,但也很难确保没有用户使用。我们的解决方案是在dict.js文件添加时间戳,每次更改后,文件名都会改变,然后浏览器就会强制下载文件。具体可以看看伟大的jym关于浏览器缓存和webpack打包添加时间戳的文章。
本地存储的最大优点是:读取快速,你不需要通过接口请求。其次是可以离线查看,不用内网登录到系统也可以查看修改字典内容,每次要修改的时候都需要CV仔来,大大提升CV仔的地位(🐂🐎bushi)
2. 远端
本地存储对于我们CV仔那么方便为什么要远端呢?
-
危险!危险!危险,好久不见。。。离题了,将数据存储在本地,防君子不防小人,这也是前端的痛楚,就像页面显式水印一样,很容易被获取和修改字典内容。其次,有些公司会进行漏扫,如果字典中存在一些敏感字段,尽管本质不是敏感信息也会被漏扫出来。
-
离线修改:因为字典在本地中,所以每次的修改都是需要前端操刀。一旦有什么变动,后端:那个前端,某某字段帮我改下,修改频繁的话还要重复启动项目,真的好烦(主要是项目太多电脑会卡😭),其次,修改时间也可能是三更半夜,严重影响CV仔的睡眠,最后,如果前端分支多的话,并且分支管理不好,就会陷入同步地狱,真的很痛苦。 实现: 正常系统会有专门的系统管理模块,其中包含字典管理,对字典进行CRUD操作。字典是存储在数据库中,每次使用的时候进行请求,实际上就是和普通的接口请求一样的,由后端提供一个统一的字典查询接口,然后前端传入对应字典的名称,去获取字典内容
实现:
data() {
return {
dictList: {
DICT_OPTS: [],
DICT_D1: [],
DICT_D2: [],
//...
}
}
}
async mounted() {
await this.getDicts()
console.log(123)
}
methods:{
async getDicts() {
await Promise.all([
// async显示返回一个Promise,我们不需要知道返回的内容,只要等待请求结束就行
Object.keys(this.dictList).map(async (key) => {
// 接口请求
const res = await getDicts(key)
this.dictList[key] = res.data
})
])
}
}
优点:CV仔不再是🐂🐎,可以睡个好觉(很大程度解决了本地存储的痛点) 缺点:CV仔的地位存在感大大降低😭😭(数据在后端,后端也可以做,我们只要频繁调用,不断给后端请求压力,后端改字典的需求我们一例拒绝)
3. 本地+远端
如果每次进入页面的时候都要去请求一次数据字典,就会造成很多重复的请求,我们可以使用本地+远端,字典源是在远端数据库上,每获取一个字典就将字典进行本地存储,如果本地没有字典内容,就去远端请求,如果本地有字典内容,就直接使用本地的。 实现(只提我用过的):
-
请求放在登录的时候,去获取所有的数据,然后存到localStorage中,存到localStorage,相比于session主要是存储时间长一些,用户多次登录时,不要进行重复请求。我们每次使用使用只要去localStorage中取就可以了。也可以将localStorage的数据统一暴露到一个全局变量去使用,比较方便。
回复大聪明提出的问题:
什么,你说如果这样后端数据更新前端不会同步?定时轮询会吧,一力降十会。
什么,你说这样子存到local中的优点(请求少就没用了)?那就给后端压力?问他在登录时候返回一个版本号或者更新时间之类的标识符去区分后端版本会吧,然后将这个版本号作为判断依据,不同版本才要进行请求。正常情况下每次的修改后上线都是一次新的发版,走完整流程,是不能直接在线网更改的,如果公司流程不严格的话,还要使用这个方法,不如直接叫后端在登录的时候将字典信息和用户信息一起返回更方便,前端还不用手动请求,只要存储下就行.
-
进入页面的时候去请求,然后将请求结果存到vuex中,然后使用vuex中的数据,这样每次进入页面的时候,就直接使用vuex中的数据,不需要请求。遇到没有的字典(或过期)才进行请求。具体实现(mixin + vuex),vue3使用hook,组合式api。
// store/dict.js
import { getDictByCode } from '@/api/dict' /* 获取api的接口*/
const VALID_TIME = 1000 * 60 * 10 /* 设置有效时间10min */
const state = {
dicts: {}
}
const mutations = {
SETDICT(state, {key, data, expired}) {
state.dicts[key] = {
data,
expired
}
}
}
const actions = {
/**
* key: 字典代码,
* refresh: 是否强制刷新
*/
getDicts({commit}, {key, refresh = false}) {
const current = new Date().getTime()
const dict = state.dicts[key]
if (item && !refresh && item.expired > VALID_TIME) {
return item.data
} else {
getDictByCode(key).then(res => {
if (res.data) {
// 可以对data根据一些字段进行筛选之类的
const data = res.data.filter(_ => { return true })
const expired = current + VALID_TIME
commit('SETDICT', {key, data, expired})
return data
} else {
return []
}
})
}
}
}
// mixin/dictMixin.js
import { mapActions } from 'vuex'
methods: {
...mapActions({getDict:'dict/getDicts'}),
async getDicts() {
if (this.dictList) {
await Promise.all(
Object.keys(this.dictList).map(async (e) => {
const res = await this.getDict({ key: e })
this.dictList[e] = res
}),
)
} else {
console.warn('dictList is null')
}
}
}
// vue文件中使用
import dictMixin from '@/mixin/dictMixin'
mixins: [dictMixin],
data() {
return dictList: {
DICT_OPTS: [],
DICT_D1: [],
DICT_D2: [],
}
}
async mounted() {
await this.getDicts()
}
4. 总结
字典的实现方式很多,我也只是举了些我用过的,谈了些我的小小看法。甚至举例的代码中还有缺陷,希望看到了可以指正。具体使用哪些实现方式还是要根据自身的,不然就是自己给自己加工作量,如果你的公司有工时要求,当我没说,狠狠的用麻烦的。