element的Form表單就應該這樣用

語言: CN / TW / HK

theme: arknights

我報名參加金石計劃1期挑戰——瓜分10萬獎池,這是我的第1篇文章,點擊查看活動詳情

最近在做一系列後台管理系統,其中用的最多的就是表單和表格了。這裏講一下我最近對錶單封裝的思考。 以下是我的設計思路以及具體實現,我使用的是vue3+element-plus,因此這個組件也是以這兩個庫為基礎。

已上傳npm https://www.npmjs.com/package/@webszy/zform

設計目標

配置化

我們希望把表格的內容,驗證規則,甚至於表單的樣式,格式都能更規則化,配置化,這樣後續我們可以通過構造json去實現一個表單,甚至可用實現拖拽式的構造表單。

參數簡單

儘量減少json的層級,減少json的參數,字段更加語義化。

自由度

json其實是一套自由度的很少的規則,但是vue則我們提供更多的自由度,比如h函數,比如動態組件,利用這些方法我們可以實現更高的自由度。

我的實現過程

表單項的格式設計

首先第一步,我們先設計一個基礎的格式,在這個JSON裏,字段名都是很簡單的英文單詞,我專門把驗證的規則rule放到每個子項裏來,這也比較符合直觀。 js const oneItem = { key: 'title', title: '小説名', component: 'el-input', props: { placeholder: '請輸入姓名' }, rule: [{ required: true, trigger: 'blur', message: '必填項' }], } 在這個格式裏面,比較重要的主要是2個,keycomponentkey其實就是你表單裏數據的字段名,而component則是你指定的編輯組件,在這裏我們可以直接使用字符串,但其實這裏可以通過vue的動態組件實現更靈活的應用,比如我們換一個組件庫的input組件 js import { Input } from '@varlet/ui' import '@varlet/ui/es/input/style/index.js' const oneItem = { component: Input } 這時候,我們就需要動態組件去渲染它,因此我們可以這樣寫去渲染,當component是一個字符串,比如el-input的時候,我們渲染elementinput組件,至於v-model這些我就省略了 js <el-form-item v-for="item in items" :key="item.key"> <el-input v-if="item.component === 'el-input'" /> <component v-else :is="item.component" /> </el-form-item>

v-bind的妙用

每個組件庫的組件參數都不一樣,而且有些屬性我們可能並不使用,比如el-input有這個屬性prefix-icon,是一個前綴圖標,別的組件庫不一定有啊,那到我們需要把所有組件庫的所有屬性都寫在json? 我在之前的json中設計了以個props字段,這裏面就是存放的是組件庫的屬性,或者是我們需要給組件傳的值. 這時候,vue給我們提供了一個很方便的功能,直接使用v-bind傳入一個對象,他就自動會幫我們把屬性綁定。 比如這樣寫 const props = {a:1,b:2} <el-input v-if="item.component === 'el-input'" v-bind="props" /> vue就會自動處理為下面這種, 這就是v-bind的妙用。當然運用renderFunction也可以實現這個效果,諸君可以自己嘗試一下 <el-input v-if="item.component === 'el-input'" v-bind:a="props.a" v-bind:a="props.b"/>

computed的妙用:實現v-model

下面我們來看一下數據的問題,vue中提供了方便v-model,方便我們修改的值能實時響應,並且我們可以自己實現一自定義v-model。 它的基本原理是這樣,我們先父傳子,然後子再通過事件告訴父組件修改這個值。大概實現就是這樣 ```js

export default{ props:[ 'modelValue', //v-model 'a' //v-model:a ],

emits:['update:modelValue','update:a'],

methods:{
    add(){
        this.$emit('update:modelValue',this.modelValue++)
        this.$emit('update:a',this.a++)
    }
}

} 但是這個代碼裏有一個問題,在vue中我們其實是無法修改props的,也就是説`this.modelValue++`會報錯,那麼如何解決這個問題呢,答案就是`computed`,`computed`其實也可以修改的,我們可以指定它的set方法,這樣就躲避了修改props的問題,從而實現了`v-model` { computed:{ num:{ get(){ return this.modelValue }, set(val){ this.$emit('update:modelValue',val) } } } } ```

useAttrs的妙用

在我的組件中有這樣一個功能,上傳。這就涉及到了回調函數的問題,也就是説我上傳完,甚至包括方法的名字,這樣才更靈活,比如我們在json中新增一個字段, { uploader: { emits: 'handleUploadCover', } } 然後我在渲染的時候會給它綁上這個事件,那麼我們如何獲取到這個事件的函數,並調用呢? <zForm @handleUploadCover="xxx" /> 在vue3中,我使用了useAttrs,需要注意的是vue3這裏似乎與vue2有些不同。vue3中,attrs獲取到的是沒有註冊的值,比如你如果在emits裏聲明瞭,在這裏就取不到了,不過這也正合我意,我們可以隨意指定事件名。 js const attrs = useAttrs() /* 返回值 { onHandleUploadCover:function(){xxx} } */ 可以看到這裏能獲取事件,只是名字略有不同,這裏大家處理一下就行了

表單驗證

表單裏最重要的就是驗證.首先在我之前的設計中,表單驗證的規則是分佈在每一個子項中,因此我們需要整合一下,這一塊我就不贅述了,也很簡單。

驗證方法我是直接使用的el-form的驗證,只是封裝了一下罷了。 需要注意的是,如果你用的是script setup,需要使用defineExpose導出這個方法 const validate = ()=> new Promise((resolve) => { this.$refs.form.validate((isValid) => resolve(isValid)); }) defineExpose({ validate })

上傳文件

上傳文件這裏我其實截取了一下element的上傳,只使用了它選擇的文件的功能,這塊其實可以自己實現的。 因為我上傳中間還要加很多參數,還有驗證,因此我使用了before-upload方法,並主動reject. <el-upload v-if="item.uploader" style="margin-top: 10px" :before-upload="(file) => beforeUpload(file, item)" :show-file-list="false" v-bind="item.uploader.props" > <el-button type="primary">點擊上傳</el-button> </el-upload> const beforeUpload = (rawFile, { key, uploader }) => { /*執行邏輯,其實就是調起uploader.emits裏的方法*/ return Promise.reject() }

代碼總結

我把demo放到了這裏,後續有時間我整理一下發個npm包。 https://stackblitz.com/edit/vue-m8veut?file=src/components/ZForm.vue

這次封裝這個組件,我學到了很多東西,一些比較細微的vue3知識點,比如v-bind。但我也知道這也封裝也有一些問題或者叫爭論。

到底應不應該使用json

之前看過一篇封裝el-table的文章,裏面就反對使用json,原因無非2點:json結構過於龐大,json結構不利於接手代碼的人使用。

  • 先説第二點,我覺得通過一個好的結構定義是可以緩解這個問題的,但是難道你函數式封裝就沒有學習成本了?我覺得json封裝其實每次就是複製黏貼,反而學習成本更低,但是開發成本會更高,你需要處理各種錯誤的值,錯誤的結構,因此結構越簡單越好,甚至可以拍平。

  • json並不龐大,龐大的是我們的表單,如果你表單裏幾百個條目,你怎麼樣寫都只會龐大,因此還是建議分割表單,及時上報。

需不需要v-model

在我這次封裝中,我把數據通過v-model實時返回了,但是當我寫到結尾的時候,我覺得表單的數據並不需要實時,因為我們需要的不是實時的數據,而是驗證後的正確數據。因此我覺得我們可以暴露出一個getData方法,返回驗證正確的數據。

性能問題

實際使用中,我發現這樣封裝似乎有點卡,目前暫時不知道是哪裏的問題,有待研究