前言
我以前做的项目是做客服的工单,里面有很多的工单类型,技术栈是基于Vue2,主要是这个项目创建比较早。在创建工单的时候,里面会有很多的表单字段,并且不同类型的工单对应的字段也各有差异。因此工单在设计之初,就使用了数据字典
的方案,利用数据字典
,前端就不用为表单每个字段写下拉选项对应的数据,直接请求该表单对应的字典数据
即可,以及可直接转义表格或者表单中的字段。但同时在请求数据字典的时候可能会有比较多的请求,包含重复的字典数据
请求。我们项目采用的方案是利用Vuex进行数据字典的缓存和过滤重复请求。
后面我们也做了工单的H5外链,也遇到了这种字典的问题。再加上我最近看了一些有关于数据字典相关的文章,自己准备总结一下,便于记忆。
什么是数据字典
数据字典
是项目中常用的数据维护方式之一,其优势在于用户可以自行配置数据类型,并且在不需要更新系统的情况下,将数据下发到客户端
。
字典是一种数据结构,它由键值对组成,其主要特点是键与值之间是一一对应的关系
。在字典中,键是唯一且无序的,而值可以重复。键通常用于在前后端之间进行数据传输或在代码中进行逻辑判断,而值则用于向用户展示相关信息。因此,字典提供了一种便捷的方式来存储和管理数据,同时确保了数据的唯一性和关联性。
一种常见的实现方式是在后台管理系统中设置一个数据字典模块,服务端提供一套用于对数据字典进行增删查改的维护接口
。客户端可以通过服务端提供的通用数据字典查询接口,提供数据字典码,从而获取数据列表,以便用户在下拉选择中使用。这种方法使得数据的管理和更新变得更加灵活和高效,同时也为用户提供了更好的定制化和选择性。
如果系统中,包含大量的单选或者多选下拉框,建议使用数据字典方案。
举个最简单的例子,表单有个字段是单选选择是否的。那它对应的数据字典对应:
json
[
{ key: "1", value: "是" },
{ key: "2", value: "否" },
];
实现方案
维护在客户端
将数据字典维护在客户端(即前端)与将其维护在服务器端相比,有一些优缺点和适用场景。
优点
- 减少网络请求:如果数据字典包含了前端应用所需的大量静态数据,将其维护在客户端可以减少对服务器的请求,提高应用性能和响应速度。
- 离线访问:客户端数据字典可以使应用在离线状态下仍然能够访问和展示数据,提高了应用的稳定性和用户体验。
- 减轻服务器负担:将数据字典放在客户端可以减轻服务器的负担,特别是对于大量用户同时访问的应用。
缺点
- 安全性:将数据字典维护在客户端可能会面临安全风险,特别是对于敏感数据或需要保密的数据。因为客户端数据可以被用户直接访问和修改,存在被篡改或窃取的风险。
- 维护难度高:字典表必须与后端保持一致,维护难度较高,稍有疏忽就可能导致前后端数据不匹配。尽管字典不经常被修改,但这并不意味着永远不会发生变更。一旦字典发生修改,前端就需要进行代码更新、打包和部署等操作,无形中增加了维护难度。
- 占用客户端资源:大规模的数据字典可能会占用较多的客户端资源,尤其是对于移动端设备来说可能会影响应用的性能和用户体验。
- 前端背锅:出现1或者2的问题,肯定领导会找你,唉,锅都是前端背的。
适用场景
- 静态数据:如果数据字典中的数据相对静态,不经常变化,并且对安全性要求不高,那么将其维护在客户端是一个不错的选择。
- 离线应用:对于需要在离线状态下也能正常访问数据的应用,客户端数据字典可以发挥作用。
总的来说,将数据字典维护在客户端适用于静态数据、离线应用和需要减轻服务器压力的情况。然而需要注意的是,对于涉及安全性、数据同步和资源占用等方面的考量,需要综合权衡才能确定是否将数据字典维护在客户端。
登录一次性返回所有数据字典
这种方案是将数据字典维护在服务端,可以在客户端页面进行数据字典进行配置。
用户在登录成功后,或者在已经登录状态时,刷新页面,会调用一次数据字典接口获取所用的数据字典,并进行缓存
。当使用的时候,直接从缓存中获取即可。
这种方案也是比较常用的,可以一次性返回所有数据字典,减少后台接口的请求,也不用考虑接口重复的问题。但这种方案一般适用于数据字典数据量不大的情况
。如果数据字典数据量过大,增加加载和处理数据的时间和成本,也会出现很多字典不使用,造成浪费的问题。
同时针对数据字典实时修改
的问题,可以通过刷新进行数据字典同步
。
使用哪个请求哪个
这种方案是
只有在要使用的时候,才会通过后台接口,请求对应的数据字典,并进行缓存
。例如:表单下拉选择框,表格字段的转义。
这种方案适用于有很多数据字典的情况。用什么请求什么,如果有缓存,直接使用缓存结果即可
。这种方案也是比较常见的,也是我们项目正在使用的方案。
但这种方案也存在两个问题:
- 要处理
重复请求
的问题,因为在表单中可能存在不同的表单字段使用相同的数据字典,而且可能相同的接口不少。所以要处理重复请求的问题。 字典更新
问题:字典一般是由业务进行维护,一般不进行修改。如果有修改,也只能通过本地刷新来更新数据字典。字典更新问题是数据字典,很难解决的问题,因为前端不知道什么时候字典会进行更新。
我看有的方案是在存储的数据字典中加一个调用的次数,如果次数很多,说明经常使用,当使用次数达到一个值时,就会重新进行请求。个人感觉这种方案治标不治本,甚至不必要,例如是否
这个字典,一般是经常使用的,但这种一般不会更新。
对于第二个问题,大家看谁有更好的方案,可以进行讨论
。
缓存位置方案
在前端将数据字典存储在本地(即客户端)有一些优点和缺点,以下是一些可能的优缺点:
优点:
- 快速访问: 将数据字典存储在本地可以减少对服务器的请求次数,提高数据的访问速度。
- 离线访问: 当用户处于离线状态时,本地存储的数据字典仍然可以被访问,提高了应用程序的可用性。
- 减轻服务器压力: 将数据字典存储在本地可以减少服务器的负载,降低服务器的压力。
- 减少网络流量: 不需要每次都从服务器获取数据字典,可以减少网络流量和数据传输成本。
缺点:
- 安全风险: 存储在客户端的数据可能受到篡改或窃取的风险,尤其对于敏感数据或需要保密的数据,安全性不如服务器端存储。
- 数据同步: 如果数据字典发生变化,需要确保所有客户端都及时更新,否则可能导致数据不一致的问题。
- 存储限制: 不同的浏览器和存储机制都有各自的存储限制,可能会受到容量限制或性能影响。
- 客户端资源占用: 大规模的数据字典可能会占用较多的客户端资源,特别是对于移动端设备来说可能会影响应用的性能和用户体验。
综上所述,将数据字典存储在本地可以提高应用程序的性能和可用性,但也需要考虑数据同步、安全性、存储限制和数据一致性等方面的问题。在具体应用场景中,需要综合考虑这些因素,权衡利弊,选择合适的存储方式。
但是将数据字典存储在本地是利大于弊
的,我们只能通过合适的本地存储方案,降低它的缺点出现的可能性
。
将数据字典存储在哪里,一般主要有两种方案进行选择,分别是
:
本地存储
:localStarge/sessionStrage框架自带的状态管理库
:例如Vue的Vuex/Pinia,React的Redux/Mobx
下面我们就针对上述两种方案进行讨论。
localStarge/sessionStrage
将数据字典存储在localStorage的优缺点如下:
优点:
- 持久性存储: localStorage中存储的数据在浏览器关闭后仍然保留,可以实现持久性存储,适合长期保存数据。
但是对于数据字典而言,持久性存储可能会导致数据字典无法更新同步的问题。
- 容量大: localStorage的存储容量相对较大,一般在5MB左右,适合存储较大量的数据字典。
- 简单易用: 使用localStorage进行数据存储非常简单,只需使用JavaScript的localStorage API即可实现数据的读写操作。
缺点:
- 安全性: localStorage中存储的数据对用户可见,存在被篡改或窃取的风险,不适合存储敏感数据。
- 同步问题: 当数据字典需要频繁更新时,需要考虑如何与服务器端数据保持同步。
- 单线程: localStorage的读写是单线程的,可能会影响性能,特别是在大量数据读写时。
综上所述,将数据字典存储在localStorage的优点是数据永久存储、跨页面访问、容量较大和简单易用,但缺点是安全性不足、同步问题和存储限制。在使用localStorage存储数据字典时,需要注意数据安全性和同步更新的问题,并合理利用localStorage提供的功能。
将数据字典存储在sessionStorage的优缺点如下:
优点:
- 临时存储: 数据存储在sessionStorage中是临时性的,仅在当前会话期间有效,当用户关闭浏览器标签或窗口时数据会被清除。
- 跨页面访问: 存储在sessionStorage中的数据可以在同一浏览器窗口或标签页中共享和访问。
- 简单易用: sessionStorage提供了简单的API,方便存储和获取数据。
缺点:
- 数据丢失: 数据存储在sessionStorage中会在用户关闭浏览器标签或窗口时被清除,可能导致数据丢失。
- 安全性: 与localStorage一样,存储在sessionStorage中的数据也存在安全性问题,容易受到XSS攻击。
总结而言,将数据字典存储在localStarge中的最大的缺点是对用户可见,可能被篡改;以及数据同步的问题,因为这两种方案都不能设置过期时间,只能另外设置:例如监听刷新页面时,将本地缓存进行清空和重新存储。
状态管理库
将数据字典存储在前端框架的状态管理库中,它有什么优缺点呢。这里以Vue的Vuex进行举例。
将数据字典存储在Vuex的优缺点如下:
优点:
- 集中管理: Vuex可以帮助前端集中管理数据字典,统一存储和更新数据,方便数据的管理和维护。
- 响应式: 存储在Vuex中的数据会响应式地更新,当数据发生变化时,相关组件会自动更新。
- 数据共享: 存储在Vuex中的数据可以在整个应用程序中共享和访问,不受页面或组件的限制。
- 状态管理: Vuex提供了一套完整的状态管理机制,可以帮助前端更好地管理应用程序的状态。
缺点:
- 复杂性: 使用Vuex需要学习和理解其概念和机制,对于小型项目可能会增加开发成本。
- 适用范围: 对于简单的数据字典存储需求,引入Vuex可能显得过于复杂,不够轻量级。
- 性能: 在存储大量数据时,Vuex可能会影响应用程序的性能,需要谨慎使用。
这些缺点,我们在上一章节讲了,是使用大量数据字典的情况,所以他的1和2缺点可以忽略。
我们可以看到,将数据存储在前端框架的状态管理库中,
可以避免对用户可见,避免用户篡改
。时如果不对数据字典进行持久性存储,当页面刷新的时候,会重新存钱数据字典,缓解数据字典不同步的问题
。
这样我们就可以解决或者缓解上一小节,将数据字典存储在本地的问题。
所以我们一般也是选择这种方案。
数据字典Store方法封装
在上一章节我们是确定了使用前端框架自带的数据状态管理的方案,那在状态管理库中怎样进行封装呢?
Vuex数据字典Store方法封装
这里我们也以Vue框架的Vuex进行示例,其他状态管理库也是相类似的。
我们在上文中也讲到了,数据字典可能存在重复请求的问题。这里我们按照以下流程进行封装:
- 首先判断它对应的数据字典是否已经存在,若存在,则直接使用缓存数据
- 如果不存在,说明要进行网络请求,得到对应的数据字典数据。首先判断它是否是重复请求,如果是,则返回相同字典的请求数据。(这里我们是使用dicCode字段判断是重复请求的,其中dicCode是字典对应的唯一编码值)
- 如果不是,首先加入请求集合,便于后面判断是否是重复请求。请求成功后,将数据进行存储,并删除请求集合里的自身对应的数据。
完整代码如下:
js
import { getDicts as getDicDataList } from '@/api/system/dict/data'
const state = {
dictDataList: {},
requestDictList: new Set()
}
const getters = {
getDictLabel: (state) => (dicType, dictValue) => {
const dictList = state.dictDataList[dicType] || []
if(dictValue && dictValue.includes(",")){
const arr = val.split(",");
return arr.map(item => dictList.find(o => String(o.dicItemCode) === String(item))).join(",");
}
const dictItem = dictList.find(item => String(item.dicItemCode) === String(dictValue)) || {}
return dictItem.dicItemName || dictValue
}
}
const mutations = {
setDictDataList (state, dictDataList) {
state.dictDataList = { ...state.dictDataList, ...dictDataList }
},
pushRequestDictList (state, dictType) {
state.requestDictList.add(dictType)
},
spliceRequestDictList (state, dictType) {
state.requestDictList.delete(dictType)
}
}
const actions = {
async getDictData ({ commit, state }, dictType) {
if (Array.isArray(dictType)) {
// 多个数据字典请求
const dicTypesRequest = dictType.filter(dictType => !state.dictDataList[dictType])
if (dicTypesRequest.length === 0) {
return state.dictDataList
}
const { data } = await getDicDataList(dicTypesRequest)
commit('setDictDataList', data)
return state.dictDataList
} else if (typeof dictType === 'string') {
if (!state.dictDataList[dictType]) {
if (!state.requestDictList.has(dictType)) {
commit('pushRequestDictList', dictType)
const { data } = await getDicDataList(dictType)
commit('setDictDataList', { [dictType]: data || [] })
commit('spliceRequestDictList', dictType)
return data
}
} else {
return state.dictDataList[dictType]
}
}
},
updateDictData({ commit, state }, dictType) {
if (!state.requestDictList.has(dictType)) {
commit('pushRequestDictList', dictType)
return getDicDataList(dictType).then(({ data }) => {
commit('setDictDataList', { [dictType]: data || [] })
return data
}).finally(() => {
commit('spliceRequestDictList', dictType)
})
}
}
}
export default {
namespaced: true,
state,
getters,
mutations,
actions
}
代码分析讲解:
-
state
dictDataList
:存储字典数据的对象。requestDictList
:存储正在请求中的字典类型的集合。
-
getters
getDictLabel
:根据字典类型和值来获取对应的字典标签,如果值为多个则返回多个标签。
-
mutations
setDictDataList
:用于更新dictDataList
中的字典数据。pushRequestDictList
:将请求中的字典类型添加到requestDictList
中。spliceRequestDictList
:从requestDictList
中移除已完成请求的字典类型。
-
actions
getDictData
:根据字典类型获取字典数据,如果已有数据则直接返回,否则发起请求获取数据。updateDictData
:更新字典数据,即使已有数据也会发起请求并更新。
总体来说,这个模块实现了以下功能:
根据字典类型获取对应的字典数据,并且可以处理多个字典类型的请求。
在数据请求过程中对重复请求进行了控制,避免重复请求相同的字典类型。
请注意,以上代码是一个基本的Vuex模块,它使用了ES6的语法和异步操作。在实际使用中,需要根据具体的业务需求和后端接口进行适当的调整和扩展。
lodash.memoize
在上述的代码中,我们是使用请求集合来判断是否是重复的数据字典请求。后面看了一些文章,发现可以使用loadsh函数中的memoize方法进行重复请求的判断。既缓存又像平常请求一样调用。
lodash.memoize
是由流行的JavaScript实用程序库Lodash提供的一个函数。它用于对函数的结果进行记忆化,这意味着它根据传递给它的参数缓存函数的结果。对具有相同参数的函数的后续调用将返回缓存的结果,而不是重新执行函数
。
以下是关于lodash.memoize
如何工作的简要概述:
-
它接受两个参数:
func
:要进行记忆化的函数。resolver
:一个可选函数,根据传递给记忆化函数的参数确定存储结果的缓存键。
-
当首次使用特定参数调用记忆化函数时,
lodash.memoize
会计算该参数的函数结果,并将其存储在缓存中。 -
对具有相同参数的记忆化函数进行后续调用将返回缓存的结果,而不是重新执行函数。
-
如果使用不同参数调用记忆化函数,它将为这些参数计算结果并分别进行缓存。
以下是如何使用lodash.memoize
的简单示例:
js
const memoizedFunction = _.memoize((arg1, arg2) => {
console.log('执行带参数的函数:', arg1, arg2);
return arg1 + arg2;
});
console.log(memoizedFunction(1, 2)); // 这将执行函数并返回3
console.log(memoizedFunction(1, 2)); // 这将返回缓存的结果(3)而不重新执行函数
console.log(memoizedFunction(3, 4)); // 这将使用新参数执行函数并返回7
在这个示例中,传递给lodash.memoize
的函数将仅对每组唯一参数执行一次,并且对具有相同参数的后续调用将返回缓存的结果。
方法封装
js
import store from '@/store'
// 数据字典请求
export function getDictData (dictType) {
return store.dispatch('dataDictionary/getDictData', dictType)
}
// 数据字典转义
export function getDictLabel (dictType, dictValue) {
// 初始化 拉取字典数据
getDictData(dictType).catch(error => {
})
return store.getters['dataDictionary/getDictLabel'](dictType, dictValue)
}
通过全局方法的封装,我们可以更加方便地在表单获取下拉数据或者表格字典转义中使用。