前端vue2?element?ui高效配置化省時又省力
前言
這篇文章是筆者曾經(jīng)全盤負(fù)責(zé)了接近一年的廣告投放系統(tǒng)沉淀下來的開發(fā)經(jīng)驗,大家各取所需,不喜勿噴~當(dāng)然啦,有自己的見解、更好的建議的大牛朋友們,我們評論區(qū)見~hhh,大??多提點高見,讓筆者繼續(xù)學(xué)習(xí)繼續(xù)進步!
本文從 場景介紹 、 設(shè)計&實現(xiàn) 、 性能優(yōu)化 三個部分進行講解。筆者當(dāng)時的技術(shù)棧是 vue2
+ element-ui
,文章案例也是(其實大家不必糾結(jié)于技術(shù)棧,掌握設(shè)計的思路和理念,什么框架都是一樣的)。
主要能解決的問題就是 提高代碼復(fù)用能力、提升開發(fā)效率,特別是需要開發(fā)多個大型表單系統(tǒng)的,配置化可以極大的提升效率,讓你上班摸魚不再是夢想!為了早點下班,我們接著往下看吧!
一、場景介紹
1. 業(yè)務(wù)場景
如何定義「巨型」表單,這個因人而異。但如果只是一些:收貨人信息、登陸、注冊的這種比較簡單的表單,那肯定算不上巨型,直接常規(guī)開發(fā)寫模版就好了,沒有必要為了配置化而配置化~
從筆者的理解出發(fā),表單項非常多,比如筆者曾經(jīng)負(fù)責(zé)的「投放系統(tǒng)」,隨隨便便提交時都會涉及幾十甚至上百個字段,這樣整個表單會有幾十、上百個表單項組成,這就算得上是巨型表單了。
先給大家看看成品的其中的一小塊截圖~
別看到截圖好像表單項也就那樣,根據(jù)右欄數(shù)起來共40+個,但是這個只是初期版的,還有很多字段是沒接進來的;
而且很多表單項之間有聯(lián)動、可增刪,還有很多表單項是隱藏的相信你很難想象,其實你只要進行簡單的配置,就能實現(xiàn)上圖的界面。比如下圖的 js對象 就是上圖的其中幾個表單項的配置:
大家已經(jīng)不難看出,配置化思路其實就是對表單項進行了抽象,制定了一份協(xié)議去描述每個表單項。具體對象中的每個屬性有什么用,這個筆者稍后講自己的設(shè)計思路時再詳細(xì)介紹~
這時候你一定會有疑問,為什么要抽象、為什么配置化的方案更好,我們接著往下看~
2. 配置化想法萌生
高復(fù)用、好維護。是的,筆者用配置化方式開發(fā)表單,完完全全就是為了高復(fù)用、可維護性,然后提升開發(fā)效率,解放生產(chǎn)力。
- 高復(fù)用:相似的業(yè)務(wù)邏輯進行統(tǒng)一處理,復(fù)用在相似領(lǐng)域的業(yè)務(wù)場景。如果說投放系統(tǒng)只需要接入一個渠道,那真的寫
template
一把梭就完了。但事實上卻不是這樣的,當(dāng)你接入了第一個facebook
,你發(fā)現(xiàn)后面還有tiktok
、 巨量引擎 、 廣點通 等各種媒體渠道... - 可維護:實現(xiàn)配置代替開發(fā)。即使把配置抽離,交到非技術(shù)人員處,其根據(jù)協(xié)議一樣能實現(xiàn)表單項的增刪,完成業(yè)務(wù)。并不是把東西做出來就完事了。首先,渠道方會有新配置功能推出,這個是不可控的。其次,系統(tǒng)開發(fā)時并不是全字段接入,而是先接入業(yè)務(wù)方所需要的核心配置,所以后期會有很多接入新的字段需求。
接下來舉兩個例子來說說,高復(fù)用、好維護體現(xiàn)在哪里
- 表單1。代碼如下:
<el-form ref="form" :model="form"> <!-- 當(dāng)活動區(qū)域的值為 “area1” 時, 活動名稱才展示 --> <el-form-item label="活動名稱" v-if="form.area === 'area1'"> <el-input v-model="form.name"></el-input> </el-form-item> <el-form-item label="活動區(qū)域"> <el-select v-model="form.area" multiple>...</el-input> </el-form-item> </el-form>
- 表單2。雖然跟 表單1 很相似,但又存在不同。比如 表單2 的活動區(qū)域不叫 “活動區(qū)域” ,且 表單項 之間的聯(lián)動關(guān)系有所不同,我們接著使用 copy大法 來做,代碼如下:
<el-form ref="form" :model="form"> <el-form-item label="活動名稱"> <el-input v-model="form.name"></el-input> </el-form-item> <!-- label變成 “活動2”,且需要填寫name后才能操作,且是多選 --> <el-form-item label="活動2"> <el-select v-model="form.area" :disable="form.name" multiple >...</el-input> </el-form-item> </el-form>
copy大法雖然好使,但是我們的復(fù)用能力基本就沒了,所有功能都近乎是重新開發(fā),這使得非常的被動。別看上面舉例好像很輕松就能實現(xiàn),筆者說過了,我們將要開發(fā)的是一個上百項表單項的系統(tǒng),當(dāng)模版的量堆積到一定程度時,你會想吐血。好不容寫了上千行模版,以為完事了,結(jié)果再接一個新的媒體,又是從一個新的開始......并且,你要再寫一個上千行的 template
和各種表單項之間的聯(lián)動邏輯,也是很痛苦的...
所以,怎么提升復(fù)用能力 , 怎么讓復(fù)雜的表單 變得清晰好維護,就是筆者的出發(fā)點的~
二、設(shè)計 & 實現(xiàn)
1. 設(shè)計協(xié)議
首先我們思考下我們的每個表單項目需要一些什么:
- type 類型。比如
input
、select
、radio
等等 - label 表單項的名稱/描述。
- formKey 字段名。我們提交數(shù)據(jù)到后段的字段名,比如
form.name
的'name'
- value 存放表單值。表單上
v-model
所綁定的值 - options 配置項。比如配置
multiple
、disabled
、 是否顯示 等等
好了,有了以上這些點,我們試著把案例中的 表單1 用協(xié)議表達出來:
<el-form-item label="活動名稱" v-if="form.area === 'area1'"> <el-input v-model="form.name"></el-input> </el-form-item> <el-form-item label="活動區(qū)域"> <el-select v-model="form.area">...</el-input> </el-form-item>
我們可以用協(xié)議這樣去描述它
[ { type: 'el-input', label: '活動名稱', formKey: 'name', value: '', // 默認(rèn)值為空字符串 options: { vIf: [ // 表示:當(dāng) form.area === 'area1',才顯示 { relationKey: 'area', value: 'area1' } ] } }, { type: 'el-select', label: '活動區(qū)域', formKey: 'area', value: 'area1', options: { multiple: true } } ]
是不是有點內(nèi)意思了?如果把 開發(fā)巨型表單系統(tǒng) 轉(zhuǎn)換成 編寫JSON ,是不是很爽?
2. 實現(xiàn)渲染器
配置是有了,但是怎么把配置轉(zhuǎn)換成我們真實的表單呢?如果直接開干,我想大部分可能會先這樣下手,比如:
<template> <el-form-item :label="props.label"> <el-input v-if="props.type === 'el-input' && ...業(yè)務(wù)聯(lián)動邏輯" :disabled="props.disabled" v-model="props.value" ... /> <el-select v-if="props.type === 'el-select' && ...業(yè)務(wù)聯(lián)動邏輯" :disabled="props.disabled" multiple="props.multiple" v-model="props.value" ... >...</el-select> </el-form-item> </template>
好了,大家觀察一下上面的 template
中,有沒發(fā)現(xiàn)很多冗余的代碼。如果我們需要給組件傳入 props
比如例子中的 disabled
、 multiple
;控制 v-if
等等。。我們有多少個組件,這些重復(fù)的代碼就要寫多少次。如果以后有需要給所有組件傳多一個 props
,我們就要編輯n次~記?。∥覀兣渲没褪且岣咝实?,所以這樣是不行的~
在此,筆者就建議編寫 render函數(shù)。render函數(shù) 的場景 & 對應(yīng)的好處,大家可以看看 官方文檔 對其的講解~
- 這里不會深入介紹 render函數(shù) ,如果還不知道的,大家只需要記住:
Vue
只認(rèn) render函數(shù),平時我們.vue文件
寫的template
,經(jīng)過編譯之后就是 render函數(shù) - render函數(shù) 作用就是返回一個
vNode
。我們vue2
初始化項目時寫的:render (h) => h(App)
是不是就似曾相識了呢?
都說 React 寫 jsx
比 Vue 寫 template
更好寫邏輯,那我們也用 render函數(shù) ,好寫邏輯~ ?? (當(dāng)然,如果你對render函數(shù)不是特別熟悉,那么寫template也是可以的)
接下來,我們看看,如何通過render函數(shù),把我們的表單項做出來,以上述案例其中一個為例子:
<el-form-item label="活動名稱"> <el-input v-model="form.name"></el-input> </el-form-item>
這一段要怎么通過render函數(shù)表述出來?根據(jù)官方文檔,我們理清三個參數(shù)是什么就可以了:
createElement( 'div', // {String | Object | Function},一個 HTML 標(biāo)簽名、組件選項對象,或者... {}, // 一個與模板中 attribute 對應(yīng)的數(shù)據(jù)對象 [] // {String | Array},可以理解成時 children 節(jié)點 )
接著,我們直接開干:
<script> export default { name: "FormItemDemo", render(createElement) { return createElement('el-form-item', { props: { label: '活動名稱' } }, [ createElement('el-input') // input組件 ]) } } </script>
在 App組件 中引用這個 FormItemDemo組件,代碼如下
<template> <div> <el-form label-width="100px"> <FormItemDemo /> </el-form> </div> </template> <script> import FormItemDemo from "./components/FormItemDemo.vue"; export default { name: 'App', components: { FormItemDemo } } </script>
這時候,頁面上就出現(xiàn)了我們的 input表單項 了
初始工作已經(jīng)做完了,接下來的就是讓我們把 render函數(shù) 的一些動態(tài)數(shù)據(jù)用變量代替,跟我們的 配置config 結(jié)合起來。
??????注意,render函數(shù)很靈活,第一個參數(shù)可以是字符串、組件對象、function。大家不要被demo所局限,很多場景是需要我們定制一些組件,然后應(yīng)用到我們的配置化中,而不是直接使用element-ui的原生組件。筆者項目中用的組件都是經(jīng)過業(yè)務(wù)場景封裝后export出來的組件對象,再配置到type中,最后render函數(shù)接收的是一個組件對象,具備業(yè)務(wù)能力的組件。因此,我們可以在自己實現(xiàn)的組件中定制自己的業(yè)務(wù)邏輯。
3. render函數(shù) & 配置數(shù)據(jù)
要說 render函數(shù) 也不是真的完美,畢竟要自己去實現(xiàn)譬如 v-if
、 v-model
這種指令,但是沒問題,它帶給我的便利給大,所以我能接受。
正式演示配置化的實現(xiàn)時,筆者先聲明一點:這里的只是 demo 級別的,具體實戰(zhàn)到項目要根據(jù)業(yè)務(wù)場景。筆者做業(yè)務(wù)時,是對 select
、 cascader
等組件都封裝了一層。因為很多時候我們的下拉數(shù)據(jù)要去后端拿,封裝后組件可以通過傳入的 params
和 urlPath
去獲取數(shù)據(jù)。所以,大家更要關(guān)注思路,然后根據(jù)業(yè)務(wù)場景自己去思考、實現(xiàn)即可。
首先配置數(shù)據(jù)如下:
export default [ { type: 'el-input', label: '活動名稱', formKey: 'name', value: '', // 默認(rèn)值為空字符串 options: { vIf: [ // 表示:當(dāng) form.area === 'area1',才顯示 { relationKey: 'area', value: 'area1' } ] } }, { type: 'el-select', label: '活動區(qū)域', formKey: 'area', value: 'area1', options: { multiple: true }, optionData: [ // 這里模擬去后端拉回數(shù)據(jù) { label: '區(qū)域1', value: 'area1' }, { label: '區(qū)域2', value: 'area2' } ] } ]
我們把 render函數(shù) 改造后,變成這樣
<script> export default { name: "FormItemDemo", props: { itemConfig: Object // 接收配置,外部傳入 }, render(createElement) { return createElement('el-form-item', { props: { label: this.itemConfig.label // 表單項的label } }, [ // 表單組件 createElement(this.itemConfig.type, { props: { value: this.itemConfig.value // 這里是自己實現(xiàn)一個 v-model }, on: { change: (nVal) => { // 這里是自己實現(xiàn)一個 v-model this.itemConfig.value = nVal } } }, this.itemConfig.optionData && this.itemConfig.optionData.map(option => { // 這里只是 本demo 處理 el-select 的 option 數(shù)據(jù),實際大家根據(jù)具體業(yè)務(wù)來實現(xiàn)即可 return createElement('el-option', { props: { label: option.label, value: option.value } }) })) ]) } } </script>
接下來我們在 app組件 中同時應(yīng)用我們的 配置 + FormItemDemo 組件:
<template> <div> <el-form label-width="100px"> <FormItemDemo v-for="item in config" :item-config="item" /> </el-form> </div> </template> <script> import FormItemDemo from "./components/FormItemDemo.vue"; import config from "./config"; export default { name: 'App', components: { FormItemDemo }, data () { return { config } } } </script>
這時候我們看下頁面長什么樣?
ok?。?!實現(xiàn)了,接下來,我們只需要根據(jù)業(yè)務(wù)需求不斷豐富我們的 FormItemDemo
組件即可。這里,筆者會帶著大家一起實現(xiàn)一個 聯(lián)動顯示隱藏 、 下拉框多選 的功能~相信看完后,你一定有醍醐灌頂?shù)母惺?,然后就可以自己根?jù)業(yè)務(wù)去實現(xiàn)需求了。
4.豐富組件能力,實現(xiàn)業(yè)務(wù)
我們先來看第一個需求:
- 當(dāng)活動區(qū)域的值為 “area1” 時, 活動名稱才展示
分析一下這個需求,我們的 input組件 跟 select組件 聯(lián)動,所以 input組件 要獲取 select組件 的值,這時候,我們可以在 app組件 中,將整個 config
傳入 FormItemDemo組件。
再回看一下我們的配置,我們把顯示隱藏的配置放在 options.vIf
中(這里筆者設(shè)計成了一個數(shù)組,因為碰到的業(yè)務(wù)經(jīng)常存在一個表單會受到好幾個表單值聯(lián)動的),所以 FormItemDemo組件 需要用這個來判斷是否執(zhí)行本次 render
以此來實現(xiàn) v-if
。如圖所示:
筆者用了一個 computed
去實現(xiàn)這個需求。大家可以不用仔細(xì)深入,只要知道 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,直接看下結(jié)果!兩個表單項之間的聯(lián)動完成了
接下來的需求,大家自行思考下怎么實現(xiàn)即可。其實都是異曲同工的
- 控制 select 多選 、 單選
- 添加 filter 屬性 ...
好了,這樣子,基本上就大功告成了,只要我們把 FormItemDemo
的業(yè)務(wù)邏輯都實現(xiàn)了,后續(xù)不管開發(fā)N個表單系統(tǒng),我們只需要配置就完事了,摸魚也就是板上定釘?shù)氖虑榱恕?,一個優(yōu)秀的前端,怎么能這么算了呢?我們好歹也要做一點優(yōu)化是吧?
三、配置靜態(tài)化
細(xì)心的朋友可能已經(jīng)發(fā)現(xiàn)了,我們上述實現(xiàn)配置化的時候,直接把整個 config 賦值給 data ,然后在 App組件 的 el-form
中 v-for
使用,那這樣避免不了就會出現(xiàn)一些尷尬的事情,比如我們看下圖:
沒錯,就如大家所見,所有的屬性都帶上了 getter
和 setter
,這意味著,他們都被初始化成了響應(yīng)式的。由于我們的業(yè)務(wù)是非常復(fù)雜的,所以當(dāng)我們真的要用一個 config
去描述整個表單時,config
的規(guī)模遠(yuǎn)不止以上這么點,并且整個配置對象的層級可能還會比較深,如果這樣的話就可能會有性能問題了。
熟悉 Vue2
的同學(xué)都知道,初始化的時候,會對 data
做一個深度遍歷添加 get
、 set
變成響應(yīng)時數(shù)據(jù),并且在組件執(zhí)行 render函數(shù) 時,會訪問到這些對象的屬性。一旦訪問到,就會觸發(fā) data屬性 的依賴收集動作,如果無腦多的屬性時,這個 get方法
將被無腦執(zhí)行。
這肯定不符合我們這種優(yōu)秀的前端的作風(fēng)的是吧?怎么搞,優(yōu)化唄。思路我們也不自己想了,直接拿 尤大 的處理來耍吧哈哈哈。??
有深入看過 Vue2 源碼的同學(xué),對 __ob__
這個屬性一定不陌生,上面截圖也有這個屬性,但是大家發(fā)現(xiàn)沒,這個 ob屬性
卻沒有對應(yīng)的 get
、set
。讓我們打開源碼,看看 尤大 做了什么?
首先,在進行響應(yīng)式處理之前,調(diào)用了一個 def
的方法,這里 第四個參數(shù) 是沒傳的
看看 def 的具體實現(xiàn),其實就是重新定義這個對象的屬性。由于沒傳 enumerable
,所以此時 __ob__
的 enumerable
為 false
這樣有什么用?一句話概括就是無法遍歷到這個屬性,后續(xù)響應(yīng)式初始化時也會跳過這個屬性。不清楚的伙伴可以看看筆者寫的一個 demo
來加深理解:
沒錯,我們這里也是采用同樣的方式對我們的 config
進行 非響應(yīng)式 優(yōu)化。其實整個 config
數(shù)據(jù),我們只是需要保證 value
是響應(yīng)式的即可,其他很多描述性數(shù)據(jù)都是大可不必的。那我們就把其他字段進行一個優(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 的屬性都進行非響應(yīng)式優(yōu)化 Object.defineProperty(cur, [key], { enumerable: false }) } acc.push(cur) return acc }, []) }
具體就不展開介紹函數(shù)實現(xiàn)了,大家 get 到思路就 ok 了(有興趣的可以細(xì)看一下)~
此時,我們再打印 config 來看看變成什么樣了:
ok,這下就舒服了~終于大功告成了?。?!
寫在最后,其實這個是我差不多一年前的實踐了,一直在分享與不分享之間徘徊。因為這類型的文章,適用性一般般,需要有一定的業(yè)務(wù)場景應(yīng)用才能比較有用,但是嘛,說不定有跟我之前遇到一樣境況的小伙伴,又或者是大家有更好的見解建議能為我?guī)硖嵘晕疫€是決定分享出來。
平時更多的開發(fā)伙伴都會吐槽天天在業(yè)務(wù)中摸爬打滾,項目沒有亮點,這個是不可否認(rèn)也不可避免的現(xiàn)狀吧~企業(yè)本就是為了盈利生產(chǎn),不可能每時每刻都能有技術(shù)挑戰(zhàn)的活下來,更多時候我們可能是平庸的業(yè)務(wù)中度過。但是,我們能否在平庸的業(yè)務(wù)中再拓展出更高效、更便捷的方式,說不定這就是一種突破和亮點吧。
雖然你們可能不會認(rèn)可,但是這的的確確是讓我開發(fā)效率提升大半以上的方案,并且出去面試我也是把這一條放在第一點去寫。它可能不是特別亮點,不是特別完善,但對我個人而言,我從 設(shè)計 - 實現(xiàn) - 優(yōu)化 的每一步都有自我的思考且落地,對我自己而言,它就是亮點了,也許很少人會注意到配置優(yōu)化這一步,筆者個人覺得這一步算是點睛之筆,因為深入到了細(xì)節(jié)。最后,希望能幫助到有需要的大??,大家可以早點下班,留給多的時間給自己去享受生活!沒有絕對完美的技術(shù)和方案,只有合適與否,根據(jù)場景選擇方案哈,更多關(guān)于vue2 element-ui前端配置化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue2 使用 Echarts 創(chuàng)建圖表實例代碼
本篇文章主要介紹了Vue2 使用 Echarts 創(chuàng)建圖表實例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05Vue動態(tài)構(gòu)建混合數(shù)據(jù)Treeselect選擇樹及巨樹問題的解決
這篇文章主要介紹了Vue動態(tài)構(gòu)建混合數(shù)據(jù)Treeselect選擇樹及巨樹問題的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-07-07element-ui樹形控件后臺返回的數(shù)據(jù)+生成組織樹的工具類
這篇文章主要介紹了element-ui樹形控件后臺返回的數(shù)據(jù)+生成組織樹的工具類,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03安裝vue無法運行、此系統(tǒng)無法運行腳本問題及解決
這篇文章主要介紹了安裝vue無法運行、此系統(tǒng)無法運行腳本問題及解決,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03