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

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


咨詢
建站咨詢
