前言
前后端開發(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個問題:
必須明白,后端執(zhí)行速度的瓶頸就是數(shù)據(jù)庫操作,數(shù)據(jù)庫的一大原則就是能少存東西就少存,能存字母數(shù)字就不存漢字,能存1個字就不存2個字。字典表的作用就是犧牲前端的輕便,讓后端和數(shù)據(jù)庫更輕便,這個收益是值得的。
防止
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個原因:
- 比如在template中有這么一句:
<div v-if="serviceType === 2">...</div>
請問,這里serviceType值為2,代表什么?如果這個“服務(wù)類型”字典有10多個項(xiàng),你背的過嗎?你背不過。你每次閱讀到這句代碼,你都要去字典表查一查。
其實(shí)程序界已經(jīng)有一個名詞叫“魔術(shù)值”,就是指這種突然出現(xiàn)的1、2、3……,就像看戲法一樣云里霧里,你根本看不懂它代表什么。
- 假如有一系列狀態(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ù)是輕易不會有任何變化的,那么:
后端應(yīng)利用更高效的緩存方式去緩存總接口數(shù)據(jù),而不是每次都從數(shù)據(jù)庫去查詢。
凡是對字典表的修改都應(yīng)觸發(fā)后端重新緩存數(shù)據(jù)。
五、前端封裝統(tǒng)一的格式器方法
依舊拿若依舉例,若依的范例代碼中,它是ajax每個字典表,并逐個字典表寫各自的格式方法,也就是說:
ajax('sex')...用于請求性別字典表<el-table>組件里面的formatter用sexFormatter函數(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>
-