前端🦒数据字典🦒最优方案讨论

前言

我以前做的项目是做客服的工单,里面有很多的工单类型,技术栈是基于Vue2,主要是这个项目创建比较早。在创建工单的时候,里面会有很多的表单字段,并且不同类型的工单对应的字段也各有差异。因此工单在设计之初,就使用了数据字典的方案,利用数据字典,前端就不用为表单每个字段写下拉选项对应的数据,直接请求该表单对应的字典数据即可,以及可直接转义表格或者表单中的字段。但同时在请求数据字典的时候可能会有比较多的请求,包含重复的字典数据请求。我们项目采用的方案是利用Vuex进行数据字典的缓存和过滤重复请求。

后面我们也做了工单的H5外链,也遇到了这种字典的问题。再加上我最近看了一些有关于数据字典相关的文章,自己准备总结一下,便于记忆。

什么是数据字典

数据字典是项目中常用的数据维护方式之一,其优势在于用户可以自行配置数据类型,并且在不需要更新系统的情况下,将数据下发到客户端
字典是一种数据结构,它由键值对组成,其主要特点是键与值之间是一一对应的关系。在字典中,键是唯一且无序的,而值可以重复。键通常用于在前后端之间进行数据传输或在代码中进行逻辑判断,而值则用于向用户展示相关信息。因此,字典提供了一种便捷的方式来存储和管理数据,同时确保了数据的唯一性和关联性。

一种常见的实现方式是在后台管理系统中设置一个数据字典模块,服务端提供一套用于对数据字典进行增删查改的维护接口。客户端可以通过服务端提供的通用数据字典查询接口,提供数据字典码,从而获取数据列表,以便用户在下拉选择中使用。这种方法使得数据的管理和更新变得更加灵活和高效,同时也为用户提供了更好的定制化和选择性。

如果系统中,包含大量的单选或者多选下拉框,建议使用数据字典方案。

举个最简单的例子,表单有个字段是单选选择是否的。那它对应的数据字典对应:

json 复制代码
[
  { key: "1", value: "是" },
  { key: "2", value: "否" },
];

实现方案

维护在客户端

将数据字典维护在客户端(即前端)与将其维护在服务器端相比,有一些优缺点和适用场景。

优点

  1. 减少网络请求:如果数据字典包含了前端应用所需的大量静态数据,将其维护在客户端可以减少对服务器的请求,提高应用性能和响应速度。
  2. 离线访问:客户端数据字典可以使应用在离线状态下仍然能够访问和展示数据,提高了应用的稳定性和用户体验。
  3. 减轻服务器负担:将数据字典放在客户端可以减轻服务器的负担,特别是对于大量用户同时访问的应用。

缺点

  1. 安全性:将数据字典维护在客户端可能会面临安全风险,特别是对于敏感数据或需要保密的数据。因为客户端数据可以被用户直接访问和修改,存在被篡改或窃取的风险。
  2. 维护难度高:字典表必须与后端保持一致,维护难度较高,稍有疏忽就可能导致前后端数据不匹配。尽管字典不经常被修改,但这并不意味着永远不会发生变更。一旦字典发生修改,前端就需要进行代码更新、打包和部署等操作,无形中增加了维护难度。
  3. 占用客户端资源:大规模的数据字典可能会占用较多的客户端资源,尤其是对于移动端设备来说可能会影响应用的性能和用户体验。
  4. 前端背锅:出现1或者2的问题,肯定领导会找你,唉,锅都是前端背的。

适用场景

  1. 静态数据:如果数据字典中的数据相对静态,不经常变化,并且对安全性要求不高,那么将其维护在客户端是一个不错的选择。
  2. 离线应用:对于需要在离线状态下也能正常访问数据的应用,客户端数据字典可以发挥作用。

总的来说,将数据字典维护在客户端适用于静态数据、离线应用和需要减轻服务器压力的情况。然而需要注意的是,对于涉及安全性、数据同步和资源占用等方面的考量,需要综合权衡才能确定是否将数据字典维护在客户端。

登录一次性返回所有数据字典

这种方案是将数据字典维护在服务端,可以在客户端页面进行数据字典进行配置。用户在登录成功后,或者在已经登录状态时,刷新页面,会调用一次数据字典接口获取所用的数据字典,并进行缓存。当使用的时候,直接从缓存中获取即可。

这种方案也是比较常用的,可以一次性返回所有数据字典,减少后台接口的请求,也不用考虑接口重复的问题。但这种方案一般适用于数据字典数据量不大的情况。如果数据字典数据量过大,增加加载和处理数据的时间和成本,也会出现很多字典不使用,造成浪费的问题。

同时针对数据字典实时修改的问题,可以通过刷新进行数据字典同步

使用哪个请求哪个

这种方案是只有在要使用的时候,才会通过后台接口,请求对应的数据字典,并进行缓存。例如:表单下拉选择框,表格字段的转义。

这种方案适用于有很多数据字典的情况。用什么请求什么,如果有缓存,直接使用缓存结果即可。这种方案也是比较常见的,也是我们项目正在使用的方案。

但这种方案也存在两个问题:

  • 要处理重复请求的问题,因为在表单中可能存在不同的表单字段使用相同的数据字典,而且可能相同的接口不少。所以要处理重复请求的问题。
  • 字典更新问题:字典一般是由业务进行维护,一般不进行修改。如果有修改,也只能通过本地刷新来更新数据字典。字典更新问题是数据字典,很难解决的问题,因为前端不知道什么时候字典会进行更新。

我看有的方案是在存储的数据字典中加一个调用的次数,如果次数很多,说明经常使用,当使用次数达到一个值时,就会重新进行请求。个人感觉这种方案治标不治本,甚至不必要,例如是否这个字典,一般是经常使用的,但这种一般不会更新。

对于第二个问题,大家看谁有更好的方案,可以进行讨论

缓存位置方案

在前端将数据字典存储在本地(即客户端)有一些优点和缺点,以下是一些可能的优缺点:

优点:

  1. 快速访问: 将数据字典存储在本地可以减少对服务器的请求次数,提高数据的访问速度。
  2. 离线访问: 当用户处于离线状态时,本地存储的数据字典仍然可以被访问,提高了应用程序的可用性。
  3. 减轻服务器压力: 将数据字典存储在本地可以减少服务器的负载,降低服务器的压力。
  4. 减少网络流量: 不需要每次都从服务器获取数据字典,可以减少网络流量和数据传输成本。

缺点:

  1. 安全风险: 存储在客户端的数据可能受到篡改或窃取的风险,尤其对于敏感数据或需要保密的数据,安全性不如服务器端存储。
  2. 数据同步: 如果数据字典发生变化,需要确保所有客户端都及时更新,否则可能导致数据不一致的问题。
  3. 存储限制: 不同的浏览器和存储机制都有各自的存储限制,可能会受到容量限制或性能影响。
  4. 客户端资源占用: 大规模的数据字典可能会占用较多的客户端资源,特别是对于移动端设备来说可能会影响应用的性能和用户体验。

综上所述,将数据字典存储在本地可以提高应用程序的性能和可用性,但也需要考虑数据同步、安全性、存储限制和数据一致性等方面的问题。在具体应用场景中,需要综合考虑这些因素,权衡利弊,选择合适的存储方式。

但是将数据字典存储在本地是利大于弊的,我们只能通过合适的本地存储方案,降低它的缺点出现的可能性

将数据字典存储在哪里,一般主要有两种方案进行选择,分别是

  • 本地存储:localStarge/sessionStrage
  • 框架自带的状态管理库:例如Vue的Vuex/Pinia,React的Redux/Mobx

下面我们就针对上述两种方案进行讨论。

localStarge/sessionStrage

将数据字典存储在localStorage的优缺点如下:

优点:

  1. 持久性存储: localStorage中存储的数据在浏览器关闭后仍然保留,可以实现持久性存储,适合长期保存数据。但是对于数据字典而言,持久性存储可能会导致数据字典无法更新同步的问题。
  2. 容量大: localStorage的存储容量相对较大,一般在5MB左右,适合存储较大量的数据字典。
  3. 简单易用: 使用localStorage进行数据存储非常简单,只需使用JavaScript的localStorage API即可实现数据的读写操作。

缺点:

  1. 安全性: localStorage中存储的数据对用户可见,存在被篡改或窃取的风险,不适合存储敏感数据。
  2. 同步问题: 当数据字典需要频繁更新时,需要考虑如何与服务器端数据保持同步。
  3. 单线程: localStorage的读写是单线程的,可能会影响性能,特别是在大量数据读写时。

综上所述,将数据字典存储在localStorage的优点是数据永久存储、跨页面访问、容量较大和简单易用,但缺点是安全性不足、同步问题和存储限制。在使用localStorage存储数据字典时,需要注意数据安全性和同步更新的问题,并合理利用localStorage提供的功能。

将数据字典存储在sessionStorage的优缺点如下:

优点:

  1. 临时存储: 数据存储在sessionStorage中是临时性的,仅在当前会话期间有效,当用户关闭浏览器标签或窗口时数据会被清除。
  2. 跨页面访问: 存储在sessionStorage中的数据可以在同一浏览器窗口或标签页中共享和访问。
  3. 简单易用: sessionStorage提供了简单的API,方便存储和获取数据。

缺点:

  1. 数据丢失: 数据存储在sessionStorage中会在用户关闭浏览器标签或窗口时被清除,可能导致数据丢失。
  2. 安全性: 与localStorage一样,存储在sessionStorage中的数据也存在安全性问题,容易受到XSS攻击。

总结而言,将数据字典存储在localStarge中的最大的缺点是对用户可见,可能被篡改;以及数据同步的问题,因为这两种方案都不能设置过期时间,只能另外设置:例如监听刷新页面时,将本地缓存进行清空和重新存储。

状态管理库

将数据字典存储在前端框架的状态管理库中,它有什么优缺点呢。这里以Vue的Vuex进行举例。

将数据字典存储在Vuex的优缺点如下:

优点:

  1. 集中管理: Vuex可以帮助前端集中管理数据字典,统一存储和更新数据,方便数据的管理和维护。
  2. 响应式: 存储在Vuex中的数据会响应式地更新,当数据发生变化时,相关组件会自动更新。
  3. 数据共享: 存储在Vuex中的数据可以在整个应用程序中共享和访问,不受页面或组件的限制。
  4. 状态管理: Vuex提供了一套完整的状态管理机制,可以帮助前端更好地管理应用程序的状态。

缺点:

  1. 复杂性: 使用Vuex需要学习和理解其概念和机制,对于小型项目可能会增加开发成本。
  2. 适用范围: 对于简单的数据字典存储需求,引入Vuex可能显得过于复杂,不够轻量级。
  3. 性能: 在存储大量数据时,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
}

代码分析讲解:

  1. state

    • dictDataList:存储字典数据的对象。
    • requestDictList:存储正在请求中的字典类型的集合。
  2. getters

    • getDictLabel:根据字典类型和值来获取对应的字典标签,如果值为多个则返回多个标签。
  3. mutations

    • setDictDataList:用于更新dictDataList中的字典数据。
    • pushRequestDictList:将请求中的字典类型添加到requestDictList中。
    • spliceRequestDictList:从requestDictList中移除已完成请求的字典类型。
  4. actions

    • getDictData:根据字典类型获取字典数据,如果已有数据则直接返回,否则发起请求获取数据。
    • updateDictData:更新字典数据,即使已有数据也会发起请求并更新。

总体来说,这个模块实现了以下功能:

  • 根据字典类型获取对应的字典数据,并且可以处理多个字典类型的请求。
  • 在数据请求过程中对重复请求进行了控制,避免重复请求相同的字典类型。

请注意,以上代码是一个基本的Vuex模块,它使用了ES6的语法和异步操作。在实际使用中,需要根据具体的业务需求和后端接口进行适当的调整和扩展。

lodash.memoize

在上述的代码中,我们是使用请求集合来判断是否是重复的数据字典请求。后面看了一些文章,发现可以使用loadsh函数中的memoize方法进行重复请求的判断。既缓存又像平常请求一样调用。

lodash.memoize是由流行的JavaScript实用程序库Lodash提供的一个函数。它用于对函数的结果进行记忆化,这意味着它根据传递给它的参数缓存函数的结果。对具有相同参数的函数的后续调用将返回缓存的结果,而不是重新执行函数

以下是关于lodash.memoize如何工作的简要概述:

  1. 它接受两个参数:

    • func:要进行记忆化的函数。
    • resolver:一个可选函数,根据传递给记忆化函数的参数确定存储结果的缓存键。
  2. 当首次使用特定参数调用记忆化函数时,lodash.memoize会计算该参数的函数结果,并将其存储在缓存中。

  3. 对具有相同参数的记忆化函数进行后续调用将返回缓存的结果,而不是重新执行函数。

  4. 如果使用不同参数调用记忆化函数,它将为这些参数计算结果并分别进行缓存。

以下是如何使用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)
}

通过全局方法的封装,我们可以更加方便地在表单获取下拉数据或者表格字典转义中使用。

相关推荐
JosieBook4 分钟前
【架构】主流企业架构Zachman、ToGAF、FEA、DoDAF介绍
架构
旧林84325 分钟前
第八章 利用CSS制作导航菜单
前端·css
yngsqq37 分钟前
c#使用高版本8.0步骤
java·前端·c#
.生产的驴1 小时前
SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
spring boot·后端·spring·spring cloud·微服务·架构
Myli_ing1 小时前
考研倒计时-配色+1
前端·javascript·考研
余道各努力,千里自同风1 小时前
前端 vue 如何区分开发环境
前端·javascript·vue.js
丁总学Java1 小时前
ARM 架构(Advanced RISC Machine)精简指令集计算机(Reduced Instruction Set Computer)
arm开发·架构
PandaCave1 小时前
vue工程运行、构建、引用环境参数学习记录
javascript·vue.js·学习
软件小伟1 小时前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js