新聞中心
一、場(chǎng)景介紹
1. 業(yè)務(wù)場(chǎng)景
從筆者的理解出發(fā), 表單項(xiàng)非常多 ,比如筆者曾經(jīng)負(fù)責(zé)的「投放系統(tǒng)」,隨隨便便提交時(shí)都會(huì)涉及幾十甚至上百個(gè)字段,這樣整個(gè)表單會(huì)有幾十、上百個(gè)表單項(xiàng)組成,這就算得上是巨型表單了。

先給大家看看成品的其中的一小塊截圖~
別看到截圖好像表單項(xiàng)也就那樣,根據(jù)右欄數(shù)起來(lái)共40+個(gè),但是這個(gè)只是初期版的,還有很多字段是沒(méi)接進(jìn)來(lái)的;而且很多表單項(xiàng)之間有聯(lián)動(dòng)、可增刪,還有很多表單項(xiàng)是隱藏的
相信你很難想象,其實(shí)你只要進(jìn)行簡(jiǎn)單的配置,就能實(shí)現(xiàn)上圖的界面。比如下圖的 js對(duì)象 就是上圖的其中幾個(gè)表單項(xiàng)的配置:
大家已經(jīng)不難看出, 配置化思路 其實(shí)就是對(duì)表單項(xiàng)進(jìn)行了 抽象 ,制定了一份協(xié)議去描述每個(gè)表單項(xiàng)。具體對(duì)象中的每個(gè)屬性有什么用,這個(gè)筆者稍后講自己的設(shè)計(jì)思路時(shí)再詳細(xì)介紹~
這時(shí)候你一定會(huì)有疑問(wèn),為什么要抽象、為什么配置化的方案更好,我們接著往下看~
2. 配置化想法萌生
高復(fù)用、好維護(hù)。是的,筆者用配置化方式開發(fā)表單,完完全全就是為了高復(fù)用、可維護(hù)性,然后提升開發(fā)效率,解放生產(chǎn)力。
- template facebook tiktok 巨量引擎 廣點(diǎn)通
- 可維護(hù):并不是把東西做出來(lái)就完事了。首先,渠道方會(huì)有新配置功能推出,這個(gè)是不可控的。其次,系統(tǒng)開發(fā)時(shí)并不是全字段接入,而是先接入業(yè)務(wù)方所需要的核心配置,所以后期會(huì)有很多接入新的字段需求。
接下來(lái)舉兩個(gè)例子來(lái)說(shuō)說(shuō),高復(fù)用、好維護(hù)體現(xiàn)在哪里
- 表單1。代碼如下:
...
復(fù)制代碼
- 表單2。雖然跟 表單1 很相似,但又存在不同。比如 表單2 的活動(dòng)區(qū)域不叫 “活動(dòng)區(qū)域” ,且 表單項(xiàng) 之間的聯(lián)動(dòng)關(guān)系有所不同,我們接著使用 **copy大法** 來(lái)做,代碼如下:
v-model="form.area"
:disable="form.name"
multiple
>...
復(fù)制代碼
copy大法雖然好使,但是我們的 復(fù)用能力 基本就沒(méi)了,所有功能都近乎是重新開發(fā),這使得非常的被動(dòng)。別看上面舉例好像很輕松就能實(shí)現(xiàn),筆者說(shuō)過(guò)了,我們將要開發(fā)的是一個(gè)上百項(xiàng)表單項(xiàng)的系統(tǒng),當(dāng)模版的量堆積到一定程度時(shí),你會(huì)想吐血。好不容寫了上千行模版,以為完事了,結(jié)果再接一個(gè)新的媒體,又是從一個(gè)新的開始......并且,你要再寫一個(gè)上千行的 template 和各種表單項(xiàng)之間的聯(lián)動(dòng)邏輯,也是很痛苦的...
所以,怎么 提升復(fù)用能力 , 怎么讓復(fù)雜的表單 變得清晰好維護(hù) ,就是筆者的出發(fā)點(diǎn)的~
二、設(shè)計(jì) & 實(shí)現(xiàn)
1. 設(shè)計(jì)協(xié)議
首先我們思考下我們的每個(gè)表單項(xiàng)目需要一些什么:
- inputselect radio
- label 表單項(xiàng)的名稱/描述。
- formKey 字段名。我們提交數(shù)據(jù)到后段的字段名,比如 form.name 的 'name'
- value 存放表單值。表單上 v-model 所綁定的值
- multiple disabled 是否顯示
好了,有了以上這些點(diǎn),我們?cè)囍寻咐械?表單1 用協(xié)議表達(dá)出來(lái):
...
復(fù)制代碼
我們可以用協(xié)議這樣去描述它
[
{
type: 'el-input',
label: '活動(dòng)名稱',
formKey: 'name',
value: '', // 默認(rèn)值為空字符串
options: {
vIf: [
// 表示:當(dāng) form.area === 'area1',才顯示
{ relationKey: 'area', value: 'area1' }
]
}
},
{
type: 'el-select',
label: '活動(dòng)區(qū)域',
formKey: 'area',
value: 'area1',
options: {
multiple: true
}
}
]
復(fù)制代碼
是不是有點(diǎn)內(nèi)意思了?如果把 開發(fā)巨型表單系統(tǒng) 轉(zhuǎn)換成 編寫JSON ,是不是很爽?
2. 實(shí)現(xiàn)渲染器
配置是有了,但是怎么把配置轉(zhuǎn)換成我們真實(shí)的表單呢?如果直接開干,我想大部分可能會(huì)先這樣下手,比如:
v-if="props.type === 'el-input' && ...業(yè)務(wù)聯(lián)動(dòng)邏輯"
:disabled="props.disabled"
v-model="props.value"
...
/>
v-if="props.type === 'el-select' && ...業(yè)務(wù)聯(lián)動(dòng)邏輯"
:disabled="props.disabled"
multiple="props.multiple"
v-model="props.value"
...
>...
復(fù)制代碼
好了,大家觀察一下上面的 template 中,有沒(méi)發(fā)現(xiàn) 很多冗余的代碼 。如果我們需要給組件傳入 props 比如例子中的 disabled 、 multiple ;控制 v-if 等等。我們有多少個(gè)組件,這些重復(fù)的代碼就要寫多少次。如果以后有需要給所有組件傳多一個(gè) props ,我們就要編輯n次~記住!我們配置化就是要提高效率的,所以這樣是不行的~
在此,筆者就建議編寫 render函數(shù) 。 render函數(shù) 的場(chǎng)景 & 對(duì)應(yīng)的好處,大家可以看看 官方文檔 [1] 對(duì)其的講解~
- render函數(shù) Vue render函數(shù) .vue文件 template render函數(shù)
- render函數(shù) vNode vue2 render (h) => h(App)
都說(shuō) React 寫 jsx 比 Vue 寫 template 更好寫邏輯,那我們也用 render函數(shù) ,好寫邏輯~ :stuck_out_tongue_closed_eyes: (當(dāng)然,如果你對(duì)render函數(shù)不是特別熟悉,那么寫template也是可以的)
接下來(lái),我們看看,如何通過(guò)render函數(shù),把我們的表單項(xiàng)做出來(lái),以上述案例其中一個(gè)為例子:
復(fù)制代碼
這一段要怎么通過(guò)render函數(shù)表述出來(lái)?根據(jù)官方文檔,我們理清三個(gè)參數(shù)是什么就可以了:
createElement(
'div', // {String | Object | Function},一個(gè) HTML 標(biāo)簽名、組件選項(xiàng)對(duì)象,或者...
{}, // 一個(gè)與模板中 attribute 對(duì)應(yīng)的數(shù)據(jù)對(duì)象
[] // {String | Array},可以理解成時(shí) children 節(jié)點(diǎn)
)
復(fù)制代碼
接著,我們直接開干:
復(fù)制代碼
在 App組件 中引用這個(gè) FormItemDemo組件 ,代碼如下
復(fù)制代碼
這時(shí)候,頁(yè)面上就出現(xiàn)了我們的 input表單項(xiàng) 了
初始工作已經(jīng)做完了,接下來(lái)的就是讓我們把 render函數(shù) 的一些動(dòng)態(tài)數(shù)據(jù)用變量代替,跟我們的 配置config 結(jié)合起來(lái)。
3. render函數(shù) & 配置數(shù)據(jù)
要說(shuō) render函數(shù) 也不是真的完美,畢竟要自己去實(shí)現(xiàn)譬如 v-if 、 v-model 這種指令,但是沒(méi)問(wèn)題,它帶給我的便利給大,所以我能接受。
正式演示配置化的實(shí)現(xiàn)時(shí),筆者先聲明一點(diǎn): 這里的只是 demo 級(jí)別的,具體實(shí)戰(zhàn)到項(xiàng)目要根據(jù)業(yè)務(wù)場(chǎng)景 。筆者做業(yè)務(wù)時(shí),是對(duì) select 、 cascader 等組件都封裝了一層。因?yàn)楹芏鄷r(shí)候我們的下拉數(shù)據(jù)要去后端拿,封裝后組件可以通過(guò)傳入的 params 和 urlPath 去獲取數(shù)據(jù)。 所以,大家更要關(guān)注思路,然后根據(jù)業(yè)務(wù)場(chǎng)景自己去思考、實(shí)現(xiàn)即可 。
首先配置數(shù)據(jù)如下:
export default [
{
type: 'el-input',
label: '活動(dòng)名稱',
formKey: 'name',
value: '', // 默認(rèn)值為空字符串
options: {
vIf: [
// 表示:當(dāng) form.area === 'area1',才顯示
{ relationKey: 'area', value: 'area1' }
]
}
},
{
type: 'el-select',
label: '活動(dòng)區(qū)域',
formKey: 'area',
value: 'area1',
options: {
multiple: true
},
optionData: [ // 這里模擬去后端拉回?cái)?shù)據(jù)
{ label: '區(qū)域1', value: 'area1' },
{ label: '區(qū)域2', value: 'area2' }
]
}
]
復(fù)制代碼
我們把 render函數(shù) 改造后,變成這樣
復(fù)制代碼
接下來(lái)我們?cè)?app組件 中同時(shí)應(yīng)用我們的 配置 + FormItemDemo 組件:
復(fù)制代碼
這時(shí)候我們看下頁(yè)面長(zhǎng)什么樣?
ok?。。?shí)現(xiàn)了,接下來(lái),我們只需要根據(jù)業(yè)務(wù)需求不斷豐富我們的 FormItemDemo 組件即可。這里,筆者會(huì)帶著大家一起實(shí)現(xiàn)一個(gè) 聯(lián)動(dòng)顯示隱藏 、 下拉框多選 的功能~相信看完后,你一定有醍醐灌頂?shù)母惺?,然后就可以自己根?jù)業(yè)務(wù)去實(shí)現(xiàn)需求了。
4.豐富組件能力,實(shí)現(xiàn)業(yè)務(wù)
(1)當(dāng)活動(dòng)區(qū)域的值為 “area1” 時(shí), 活動(dòng)名稱才展示
我們先來(lái)看第一個(gè) 需求:
分析一下這個(gè)需求,我們的 input組件 跟 select組件 聯(lián)動(dòng),所以 input組件 要獲取 select組件 的值,這時(shí)候,我們可以在 app組件 中,將整個(gè) config 傳入 FormItemDemo組件 。
再回看一下我們的配置,我們把顯示隱藏的配置放在 options.vIf 中(這里筆者設(shè)計(jì)成了一個(gè)數(shù)組,因?yàn)榕龅降臉I(yè)務(wù)經(jīng)常存在一個(gè)表單會(huì)受到好幾個(gè)表單值聯(lián)動(dòng)的),所以 FormItemDemo組件 需要用這個(gè)來(lái)判斷是否執(zhí)行本次 render 以此來(lái)實(shí)現(xiàn) v-if 。如圖所示:
筆者用了一個(gè) computed 去實(shí)現(xiàn)這個(gè)需求。大家可以不用仔細(xì)深入,只要知道 componentShow 的作用就是,找到聯(lián)動(dòng)的 relationKey 的 config 中的 value 值,判斷是否跟配置的一致。
computed: {
componentShow () {
const vIfArr = this.itemConfig?.options.vIf
if (!vIfArr) return true
const relationArr = this.config.filter(config => vIfArr.find(vIf => vIf.relationKey === config.formKey))
for (const relationItem of relationArr) {
const vIfItem = vIfArr.find(_ => _.relationKey === relationItem.formKey)
// 這里就是判斷 聯(lián)動(dòng)的表單值 是否不滿足 可以顯示 的條件,不滿足則不顯示
if (relationItem.value !== vIfItem.value) return false
}
return true
}
}
復(fù)制代碼模擬實(shí)現(xiàn) v-if ,只需要把上述計(jì)算屬性在 render 的開頭進(jìn)行判斷即可
ok,直接看下結(jié)果!兩個(gè)表單項(xiàng)之間的聯(lián)動(dòng)完成了
- 控制 select 多選 、 單選
- 添加 filter 屬性 ...
(2)接下來(lái)的需求,大家自行思考下怎么實(shí)現(xiàn)即可。其實(shí)都是異曲同工的
好了,這樣子,基本上就大功告成了,只要我們把 FormItemDemo 的業(yè)務(wù)邏輯都實(shí)現(xiàn)了,后續(xù)不管開發(fā)N個(gè)表單系統(tǒng),我們只需要配置就完事了,摸魚也就是板上定釘?shù)氖虑榱恕牵粋€(gè)優(yōu)秀的前端,怎么能這么算了呢?我們好歹也要做一點(diǎn)優(yōu)化是吧?
三、配置靜態(tài)化
細(xì)心的朋友可能已經(jīng)發(fā)現(xiàn)了,我們上述實(shí)現(xiàn)配置化的時(shí)候, 直接把整個(gè) config 賦值給 data ,然后在 App組件 的 el-form 中 v-for 使用 ,那這樣避免不了就會(huì)出現(xiàn)一些尷尬的事情,比如我們看下圖:
沒(méi)錯(cuò),就如大家所見, 所有的屬性都帶上了 getter 和 setter ,這意味著,他們都被初始化成了響應(yīng)式的 。由于我們的業(yè)務(wù)是非常復(fù)雜的,所以當(dāng)我們真的要用一個(gè) config 去描述整個(gè)表單時(shí), config 的規(guī)模遠(yuǎn)不止以上這么點(diǎn),并且整個(gè)配置對(duì)象的層級(jí)可能還會(huì)比較深,如果這樣的話就可能會(huì)有性能問(wèn)題了。
熟悉 Vue2 的同學(xué)都知道,初始化的時(shí)候,會(huì)對(duì) data 做一個(gè)深度遍歷添加 get 、 set 變成響應(yīng)時(shí)數(shù)據(jù),并且在組件執(zhí)行 render函數(shù) 時(shí),會(huì)訪問(wèn)到這些對(duì)象的屬性。一旦訪問(wèn)到,就會(huì)觸發(fā) data屬性 的依賴收集動(dòng)作,如果無(wú)腦多的屬性時(shí),這個(gè) get方法 將被無(wú)腦執(zhí)行。
這肯定不符合我們這種優(yōu)秀的前端的作風(fēng)的是吧?怎么搞,優(yōu)化唄。思路我們也不自己想了,直接拿 尤大 的處理來(lái)耍吧哈哈哈。:stuck_out_tongue_closed_eyes:
有深入看過(guò) Vue2 源碼的同學(xué),對(duì) __ob__ 這個(gè)屬性一定不陌生,上面截圖也有這個(gè)屬性,但是大家發(fā)現(xiàn)沒(méi),這個(gè) ob屬性 卻沒(méi)有對(duì)應(yīng)的 get 、 set 。讓我們打開源碼,看看 尤大 做了什么?
首先,在進(jìn)行響應(yīng)式處理之前,調(diào)用了一個(gè) def 的方法,這里 第四個(gè)參數(shù) 是沒(méi)傳的
看看 def 的具體實(shí)現(xiàn),其實(shí)就是重新定義這個(gè)對(duì)象的屬性。由于沒(méi)傳 enumerable ,所以此時(shí) __ob__ 的 enumerable 為 false?
這樣有什么用?一句話概括就是 無(wú)法遍歷到這個(gè)屬性,后續(xù)響應(yīng)式初始化時(shí)也會(huì)跳過(guò)這個(gè)屬性 。不清楚的伙伴可以看看筆者寫的一個(gè) demo 來(lái)加深理解:
沒(méi)錯(cuò),我們這里也是采用同樣的方式對(duì)我們的 config 進(jìn)行 非響應(yīng)式 優(yōu)化。其實(shí)整個(gè) config 數(shù)據(jù),我們只是需要保證 value 是響應(yīng)式的即可,其他很多描述性數(shù)據(jù)都是大可不必的。那我們就把其他字段進(jìn)行一個(gè)優(yōu)化~
// 優(yōu)化函數(shù)
function optimize (array) {
return array.reduce((acc, cur) => {
for (const key of Object.keys(cur)) {
if (key === 'value') continue
// 將不是 value 的屬性都進(jìn)行非響應(yīng)式優(yōu)化
Object.defineProperty(cur, [key], { enumerable: false })
}
acc.push(cur)
return acc
}, [])
}
復(fù)制代碼
具體就不展開介紹函數(shù)實(shí)現(xiàn)了,大家 get 到思路就 ok 了(有興趣的可以細(xì)看一下)~
此時(shí),我們?cè)俅蛴?config 來(lái)看看變成什么樣了:
ok,這下就舒服了~終于大功告成了?。?!
寫在最后,其實(shí)這個(gè)是我差不多一年前的實(shí)踐了,一直在分享與不分享之間徘徊。因?yàn)檫@類型的文章,不會(huì)引起很多共鳴,也不會(huì)為我?guī)?lái)流量和影響力,但是嘛,說(shuō)不定有跟我之前遇到一樣境況的小伙伴,又或者是大家有更好的見解建議能為我?guī)?lái)提升,所以我還是決定分享出來(lái)。
平時(shí)更多的開發(fā)伙伴都會(huì)吐槽天天在業(yè)務(wù)中摸爬打滾,項(xiàng)目沒(méi)有亮點(diǎn),這個(gè)是不可否認(rèn)也不可避免的現(xiàn)狀吧~企業(yè)本就是為了盈利生產(chǎn),不可能每時(shí)每刻都能有技術(shù)挑戰(zhàn)的活下來(lái),更多時(shí)候我們可能是平庸的業(yè)務(wù)中度過(guò)。但是,我們能否在平庸的業(yè)務(wù)中再拓展出更高效、更便捷的方式,說(shuō)不定這就是一種突破和亮點(diǎn)吧。
雖然你們可能不會(huì)認(rèn)可,但是這的的確確是讓我開發(fā)效率提升大半以上的方案,并且出去面試我也是把這一條放在第一點(diǎn)去寫。它可能不是特別亮點(diǎn),不是特別完善,但對(duì)我個(gè)人而言,我從 設(shè)計(jì) - 實(shí)現(xiàn) - 優(yōu)化 的每一步都有自我的思考且落地,對(duì)我自己而言,它就是亮點(diǎn)了。最后,希望能幫助到有需要的大:fire:,大家可以早點(diǎn)下班,留給多的時(shí)間給自己去享受生活!
網(wǎng)站欄目:前端配置化真香~上班又多了60%的摸魚時(shí)間
鏈接分享:http://m.5511xx.com/article/cdpdjgh.html


咨詢
建站咨詢
