Element UI字典映射系统的最佳实践

前言

前后端开发中有一个实践就是做一套字典系统,为前后端提供字典映射,尤其是后台管理系统中使用最多。比如基于Element UI的若依的字典系统,它的核心是提供这样的数据内容:

[
  {
    "dictLabel": "男",
    "dictValue": "0"
  },
  {
    "dictLabel": "女",
    "dictValue": "1"
  },
]

那么,若依系统到底是不是最佳实践呢?

我认为不全是。

我认为的最佳实践

一、管理字典的后台系统依然应该有

有人说字典表系统根本没必要,似乎有他的道理,他意思是说,前端从下拉选择了,那么传给后端,后端把存放到数据库。下次返回的时候,返回的也是汉字。这种方案核心就是不做字典,直接传值。这个方案有2个问题:

  1. 必须明白,后端执行速度的瓶颈就是数据库操作,数据库的一大原则就是能少存东西就少存,能存字母数字就不存汉字,能存1个字就不存2个字。字典表的作用就是牺牲前端的轻便,让后端和数据库更轻便,这个收益是值得的。

  2. 防止dictLabel变化导致程序出错。比如车辆进小区,从前传的是字串,后来觉得不妥,改为传,后来又觉得不妥,改为传进门,后来还是觉得不妥,改为传进场,于是,如果查询最早期的数据查到的是,查询中期的数据查到的是进门,查询现在数据查到的是进场,扯淡不?

另外,字典系统还有一个大作用就是约束开发者。既然有字典系统,那么就必须以字典系统为准,只要定死了0表示,那么任何开发者都不要“另辟蹊径”,让1代表。如果没有字典系统,团队则依然需要一个记录手段来统一字典,反而不如直接用字典系统。

二、字典表的dictValue不应当是数字或数字字符串,而应该是有意义常量

原因很简单,至少有2个原因:

  1. 比如在template中有这么一句:
<div v-if="serviceType === 2">...</div>

请问,这里serviceType值为2,代表什么?如果这个“服务类型”字典有10多个项,你背的过吗?你背不过。你每次阅读到这句代码,你都要去字典表查一查。

其实程序界已经有一个名词叫“魔术值”,就是指这种突然出现的1、2、3……,就像看戏法一样云里雾里,你根本看不懂它代表什么。

  1. 假如有一系列状态:“未付款”、“已付款”、“已接单”、“已送出”、“已送达”、“已评价”,他们的编号是从0~5。后来,发觉这一套状态不够,例如想在“已送达”和“已评价”中间插一个“已验货”,这时候它尽管流程上排在“已评价”前,但是编号上只能是6,这就造成了一种开发上的混乱。

解决方案:

我定义字典的时候这样定义,给dictValue设置有意义的英文或拼音全写或缩写,而且是大写字母,表示是常量:

[
  {
    "dictLabel": "餐饮",
    "dictValue": "CY"
  },
  {
    "dictLabel": "旅游",
    "dictValue": "LY"
  },
  {
    "dictLabel": "家政",
    "dictValue": "JZ"
  },
]

这时候,<div v-if="serviceType === 'CY'">...</div>稍加思考就知道CY是指餐饮,是不是就解决了问题?

再比如,上面提到的“车辆进小区”问题,当描述“进入”这个概念时,写汉字可以有若干种写法,现在我简写为IN,则任何时候都不会错。同理,表达出小区,我写为OUT,任何时候也不会错,即便日后真的觉得INOUT表达的也有歧义,但是因为OUT并不显示在前端,所以无所谓。

再比如,以0代表男和以1代表男都已经存在争议,时间久了,我也会怀疑自己的记忆力,到底是0还是1代表呢?所以,就以M代表,以F代表,永远不会有问题。

再比如最常用的,也不要再用10,应当用YN

三、后端提供总接口,并提供较长的协商缓存

若依并没有提供总接口,而是提供了每一个字典表的接口,这种做法根本没必要,甚至就是错误实践。若依的思路是编写每个vue文件都要去思考引入哪些字典表,少一个都不行,而字典表跟业务字段名又往往不统一,比如业务字段名是isExpired,字典名是yes_no,这种对应非常费脑子,导致程序员变成了字段调试员。

另外,若依必须先用Promise.all()请求到所有字典,then才能get表格数据,否则表格的某些依赖字典的列会有瞬间的空白,这非常蠢。

解决方案:

应当用一个总接口返回所有字典表,不要用每个字典表的接口。

      {
        "sex": [
          {
            "dictValue": "M",
            "dictLabel": "男"
          },
          {
            "dictValue": "F",
            "dictLabel": "女"
          }
        ],
        "yes_no": [
          {
            "dictValue": "N",
            "dictLabel": "否"
          },
          {
            "dictValue": "Y",
            "dictLabel": "是"
          }
        ],
        "serviceType": [
          {
            "dictValue": "SMFW",
            "dictLabel": "上门服务"
          },
          {
            "dictValue": "DDFW",
            "dictLabel": "到店服务"
          }
        ],
        "orderStatus": [
          {
            "dictValue": "UNPAID",
            "dictLabel": "未付款"
          },
          {
            "dictValue": "PAID",
            "dictLabel": "已付款"
          },
          {
            "dictValue": "ORDER_RECEVIED",
            "dictLabel": "已接单"
          },
        ]
      }

在项目初始化阶段,在beforeEach中尽早ajax这个接口,将所有字典表一股脑返回来。只有字典表get完成,才执行第一个路由导航。

现在有个问题,这个接口数据字节数会比较大,会不会拖累项目加载呢?并不会,因为这个接口内容一般情况下不会有改动,毕竟字典表不是天天变,所以绝大多数时候的请求都会是304状态码,也就是要求浏览器使用缓存,所以即便数据量比较大也无所谓。

四、后端自身也要缓存总接口数据

现在已经知道,总接口数据是轻易不会有任何变化的,那么:

  1. 后端应利用更高效的缓存方式去缓存总接口数据,而不是每次都从数据库去查询。

  2. 凡是对字典表的修改都应触发后端重新缓存数据。

五、前端封装统一的格式器方法

依旧拿若依举例,若依的范例代码中,它是ajax每个字典表,并逐个字典表写各自的格式方法,也就是说:

  1. ajax('sex')...用于请求性别字典表

  2. <el-table>组件里面的formattersexFormatter函数将'0'显示为'1'显示为

然后后面有个字典比如是教育程度字典,此时又要请求edu字典表,又要写一个eduFormatter函数来映射,很繁琐很累很不愉悦对不对?

解决方案:

针对<el-table-column>组件的formatter属性写一个公共方法,比如叫dictFormatter。这个公共方法可以放到@/src/util/dictFormatter.js里,然后在main.js全局引入。

      <el-table-column
        prop="xxx"
        :formatter="dictFormatter"
      />

Element UI给格式器函数会默认传4个参数:row, column, cellValue, index,你可以打印它们看看。其中column.property就是这个column的prop,也就是'xxx'。现在我们缺一个字典表名称,怎么传?错误的方式是:formatter="dictFormatter('sex')"'sex'会覆盖掉默认参数。正确的可以这么写:

      <el-table-column
        prop="xxx"
        dict="sex"
        :formatter="(r,c,v,i) => dictFormatter(r,c,v,i,'sex')"
      />

也就是传入高阶函数,它的作用是让函数dictFormatter执行。dictFormatter的最后一个参数此时就是字典表的名字'sex'。然后我们根据'sex'这个字典名,去总表里找数据,就很轻松了。

dictFormatter的内部代码具体我就不写了,大致是总字典表先定位到sex属性上,它是个大数组,遍历这个数组看谁的dictValue等于cellValue的值,那它的dictLabel就是你想要的字符串。

另外一,懂解构赋值的同学又不乐意了,这么写也太麻烦了,还有更简单的:

      <el-table-column
        prop="xxx"
        dict="sex"
        :formatter="(...rest) => dictFormatter(...rest,'sex')"
      />

哈哈,其实数一数字符数,一样。。。但是优势是出错概率低,不像r,c,v,i这样容易漏写。

另外二,能不能拿prop的值也当做字典的值?从而缺省掉最后那个参数?

当然!在一些场合是可以的!如果prop的值和字典的值恰好是一致的,就比如都是'sex',那么直接写成:formatter="dictFormatter"会非常爽,但是,有些公共字典就不好说了,比如yes_no字典表,用来表示“是否为儿童”也行,用来表示“是否同意”也行,prop可能是'isChildren'或者'isAgreed',但字典表名字是通用的'yes_no',显然不能直接对应上,必须写略显复杂的:formatter="(...rest) => dictFormatter(...rest,'yes_no')"

总结

  • 在一些字典名跟prop重名前提下,可以缺省一个传参,因此可以直接用:formatter="dictFormatter"

  • 不重名的话,必须用:formatter="(...rest) => dictFormatter(...rest,'sex')"

  • dictFormatter的内部代码我不会在本文提供,原理上面已经说过,大致是先判断第五个参数是否存在,并作出相应处理,没写就认为column.property的值就是字典名。根据传参或者column.property定位到总表的某属性上,属性值是个大数组,遍历这个数组看谁的dictValue等于cellValue的值,那它的dictLabel就是你想要的字符串。

  • 别忘了各种出错可能性,比如:

    • cellValue的值未定义或空怎么办(通常做法是返回空串)
    • cellValue的值不在字典表里怎么办(通常做法也是返回空串,且用console.warn('...')报个错,这种现象一般是开发测试的时候出现,或黑客捣乱的时候出现,如果后端判断不严谨的话)。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352