Element UI字典映射系統(tǒng)的最佳實(shí)踐

前言

前后端開發(fā)中有一個實(shí)踐就是做一套字典系統(tǒng),為前后端提供字典映射,尤其是后臺管理系統(tǒng)中使用最多。比如基于Element UI的若依的字典系統(tǒng),它的核心是提供這樣的數(shù)據(jù)內(nèi)容:

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

那么,若依系統(tǒng)到底是不是最佳實(shí)踐呢?

我認(rèn)為不全是。

我認(rèn)為的最佳實(shí)踐

一、管理字典的后臺系統(tǒng)依然應(yīng)該有

有人說字典表系統(tǒng)根本沒必要,似乎有他的道理,他意思是說,前端從下拉選擇了,那么傳給后端,后端把存放到數(shù)據(jù)庫。下次返回的時候,返回的也是漢字。這種方案核心就是不做字典,直接傳值。這個方案有2個問題:

  1. 必須明白,后端執(zhí)行速度的瓶頸就是數(shù)據(jù)庫操作,數(shù)據(jù)庫的一大原則就是能少存東西就少存,能存字母數(shù)字就不存漢字,能存1個字就不存2個字。字典表的作用就是犧牲前端的輕便,讓后端和數(shù)據(jù)庫更輕便,這個收益是值得的。

  2. 防止dictLabel變化導(dǎo)致程序出錯。比如車輛進(jìn)小區(qū),從前傳的是字串,后來覺得不妥,改為傳進(jìn),后來又覺得不妥,改為傳進(jìn)門,后來還是覺得不妥,改為傳進(jìn)場,于是,如果查詢最早期的數(shù)據(jù)查到的是,查詢中期的數(shù)據(jù)查到的是進(jìn)進(jìn)門,查詢現(xiàn)在數(shù)據(jù)查到的是進(jìn)場,扯淡不?

另外,字典系統(tǒng)還有一個大作用就是約束開發(fā)者。既然有字典系統(tǒng),那么就必須以字典系統(tǒng)為準(zhǔn),只要定死了0表示,那么任何開發(fā)者都不要“另辟蹊徑”,讓1代表。如果沒有字典系統(tǒng),團(tuán)隊(duì)則依然需要一個記錄手段來統(tǒng)一字典,反而不如直接用字典系統(tǒng)。

二、字典表的dictValue不應(yīng)當(dāng)是數(shù)字或數(shù)字字符串,而應(yīng)該是有意義常量

原因很簡單,至少有2個原因:

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

請問,這里serviceType值為2,代表什么?如果這個“服務(wù)類型”字典有10多個項(xiàng),你背的過嗎?你背不過。你每次閱讀到這句代碼,你都要去字典表查一查。

其實(shí)程序界已經(jīng)有一個名詞叫“魔術(shù)值”,就是指這種突然出現(xiàn)的1、2、3……,就像看戲法一樣云里霧里,你根本看不懂它代表什么。

  1. 假如有一系列狀態(tài):“未付款”、“已付款”、“已接單”、“已送出”、“已送達(dá)”、“已評價”,他們的編號是從0~5。后來,發(fā)覺這一套狀態(tài)不夠,例如想在“已送達(dá)”和“已評價”中間插一個“已驗(yàn)貨”,這時候它盡管流程上排在“已評價”前,但是編號上只能是6,這就造成了一種開發(fā)上的混亂。

解決方案:

我定義字典的時候這樣定義,給dictValue設(shè)置有意義的英文或拼音全寫或縮寫,而且是大寫字母,表示是常量:

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

這時候,<div v-if="serviceType === 'CY'">...</div>稍加思考就知道CY是指餐飲,是不是就解決了問題?

再比如,上面提到的“車輛進(jìn)小區(qū)”問題,當(dāng)描述“進(jìn)入”這個概念時,寫漢字可以有若干種寫法,現(xiàn)在我簡寫為IN,則任何時候都不會錯。同理,表達(dá)出小區(qū),我寫為OUT,任何時候也不會錯,即便日后真的覺得IN、OUT表達(dá)的也有歧義,但是因?yàn)?code>OUT并不顯示在前端,所以無所謂。

再比如,以0代表男和以1代表男都已經(jīng)存在爭議,時間久了,我也會懷疑自己的記憶力,到底是0還是1代表呢?所以,就以M代表,以F代表,永遠(yuǎn)不會有問題。

再比如最常用的、,也不要再用1、0,應(yīng)當(dāng)用Y、N。

三、后端提供總接口,并提供較長的協(xié)商緩存

若依并沒有提供總接口,而是提供了每一個字典表的接口,這種做法根本沒必要,甚至就是錯誤實(shí)踐。若依的思路是編寫每個vue文件都要去思考引入哪些字典表,少一個都不行,而字典表跟業(yè)務(wù)字段名又往往不統(tǒng)一,比如業(yè)務(wù)字段名是isExpired,字典名是yes_no,這種對應(yīng)非常費(fèi)腦子,導(dǎo)致程序員變成了字段調(diào)試員。

另外,若依必須先用Promise.all()請求到所有字典,then才能get表格數(shù)據(jù),否則表格的某些依賴字典的列會有瞬間的空白,這非常蠢。

解決方案:

應(yīng)當(dāng)用一個總接口返回所有字典表,不要用每個字典表的接口。

      {
        "sex": [
          {
            "dictValue": "M",
            "dictLabel": "男"
          },
          {
            "dictValue": "F",
            "dictLabel": "女"
          }
        ],
        "yes_no": [
          {
            "dictValue": "N",
            "dictLabel": "否"
          },
          {
            "dictValue": "Y",
            "dictLabel": "是"
          }
        ],
        "serviceType": [
          {
            "dictValue": "SMFW",
            "dictLabel": "上門服務(wù)"
          },
          {
            "dictValue": "DDFW",
            "dictLabel": "到店服務(wù)"
          }
        ],
        "orderStatus": [
          {
            "dictValue": "UNPAID",
            "dictLabel": "未付款"
          },
          {
            "dictValue": "PAID",
            "dictLabel": "已付款"
          },
          {
            "dictValue": "ORDER_RECEVIED",
            "dictLabel": "已接單"
          },
        ]
      }

在項(xiàng)目初始化階段,在beforeEach中盡早ajax這個接口,將所有字典表一股腦返回來。只有字典表get完成,才執(zhí)行第一個路由導(dǎo)航。

現(xiàn)在有個問題,這個接口數(shù)據(jù)字節(jié)數(shù)會比較大,會不會拖累項(xiàng)目加載呢?并不會,因?yàn)檫@個接口內(nèi)容一般情況下不會有改動,畢竟字典表不是天天變,所以絕大多數(shù)時候的請求都會是304狀態(tài)碼,也就是要求瀏覽器使用緩存,所以即便數(shù)據(jù)量比較大也無所謂。

四、后端自身也要緩存總接口數(shù)據(jù)

現(xiàn)在已經(jīng)知道,總接口數(shù)據(jù)是輕易不會有任何變化的,那么:

  1. 后端應(yīng)利用更高效的緩存方式去緩存總接口數(shù)據(jù),而不是每次都從數(shù)據(jù)庫去查詢。

  2. 凡是對字典表的修改都應(yīng)觸發(fā)后端重新緩存數(shù)據(jù)。

五、前端封裝統(tǒng)一的格式器方法

依舊拿若依舉例,若依的范例代碼中,它是ajax每個字典表,并逐個字典表寫各自的格式方法,也就是說:

  1. ajax('sex')...用于請求性別字典表

  2. <el-table>組件里面的formattersexFormatter函數(shù)將'0'顯示為'1'顯示為

然后后面有個字典比如是教育程度字典,此時又要請求edu字典表,又要寫一個eduFormatter函數(shù)來映射,很繁瑣很累很不愉悅對不對?

解決方案:

針對<el-table-column>組件的formatter屬性寫一個公共方法,比如叫dictFormatter。這個公共方法可以放到@/src/util/dictFormatter.js里,然后在main.js全局引入。

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

Element UI給格式器函數(shù)會默認(rèn)傳4個參數(shù):row, column, cellValue, index,你可以打印它們看看。其中column.property就是這個column的prop,也就是'xxx'?,F(xiàn)在我們?nèi)币粋€字典表名稱,怎么傳?錯誤的方式是:formatter="dictFormatter('sex')"'sex'會覆蓋掉默認(rèn)參數(shù)。正確的可以這么寫:

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

也就是傳入高階函數(shù),它的作用是讓函數(shù)dictFormatter執(zhí)行。dictFormatter的最后一個參數(shù)此時就是字典表的名字'sex'。然后我們根據(jù)'sex'這個字典名,去總表里找數(shù)據(jù),就很輕松了。

dictFormatter的內(nèi)部代碼具體我就不寫了,大致是總字典表先定位到sex屬性上,它是個大數(shù)組,遍歷這個數(shù)組看誰的dictValue等于cellValue的值,那它的dictLabel就是你想要的字符串。

另外一,懂解構(gòu)賦值的同學(xué)又不樂意了,這么寫也太麻煩了,還有更簡單的:

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

哈哈,其實(shí)數(shù)一數(shù)字符數(shù),一樣。。。但是優(yōu)勢是出錯概率低,不像r,c,v,i這樣容易漏寫。

另外二,能不能拿prop的值也當(dāng)做字典的值?從而缺省掉最后那個參數(shù)?

當(dāng)然!在一些場合是可以的!如果prop的值和字典的值恰好是一致的,就比如都是'sex',那么直接寫成:formatter="dictFormatter"會非常爽,但是,有些公共字典就不好說了,比如yes_no字典表,用來表示“是否為兒童”也行,用來表示“是否同意”也行,prop可能是'isChildren'或者'isAgreed',但字典表名字是通用的'yes_no',顯然不能直接對應(yīng)上,必須寫略顯復(fù)雜的:formatter="(...rest) => dictFormatter(...rest,'yes_no')"。

總結(jié)

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

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

  • dictFormatter的內(nèi)部代碼我不會在本文提供,原理上面已經(jīng)說過,大致是先判斷第五個參數(shù)是否存在,并作出相應(yīng)處理,沒寫就認(rèn)為column.property的值就是字典名。根據(jù)傳參或者column.property定位到總表的某屬性上,屬性值是個大數(shù)組,遍歷這個數(shù)組看誰的dictValue等于cellValue的值,那它的dictLabel就是你想要的字符串。

  • 別忘了各種出錯可能性,比如:

    • cellValue的值未定義或空怎么辦(通常做法是返回空串)
    • cellValue的值不在字典表里怎么辦(通常做法也是返回空串,且用console.warn('...')報個錯,這種現(xiàn)象一般是開發(fā)測試的時候出現(xiàn),或黑客搗亂的時候出現(xiàn),如果后端判斷不嚴(yán)謹(jǐn)?shù)脑挘?/li>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容