vue2實(shí)現(xiàn)封裝動(dòng)態(tài)表單組件
封裝組件注意點(diǎn)
- 不要為了封裝而封裝
- 只封裝不變的部分,將變化的部分通過(guò)slot或其他方式,暴露出去,交給調(diào)用者實(shí)現(xiàn)
- 為了提供封裝組件的復(fù)用率,優(yōu)先封裝為UI組件,而不是封裝為業(yè)務(wù)組件(即:封裝組件內(nèi)部使用到的數(shù)據(jù),都通過(guò)prop獲取,而不是直接通過(guò)ajax請(qǐng)求或vuex中獲?。?/li>
動(dòng)態(tài)表單動(dòng)態(tài)控制的是什么?
已知的需要?jiǎng)討B(tài)控制的場(chǎng)景:
- 在A界面顯示:A, B, C三個(gè)表單項(xiàng),B界面顯示:A,B,E,F三個(gè)表單項(xiàng)
- 在A界面默認(rèn)顯示:A,B兩個(gè)表單項(xiàng),當(dāng)A項(xiàng)的值為x時(shí),C表單項(xiàng)才顯示出來(lái)
- 在A界面默認(rèn)顯示:A,B,C三個(gè)表單項(xiàng),A,B默認(rèn)可用,C默認(rèn)禁用,當(dāng)A項(xiàng)的值為x時(shí),C表單項(xiàng)才可用
- 在A界面B表單項(xiàng)可選擇的值,依賴于A表單項(xiàng)的選擇/填寫結(jié)果
- 根據(jù)不同的分辨率每行可顯示的表單項(xiàng)數(shù)量不同
動(dòng)態(tài)表單封裝
請(qǐng)優(yōu)先考慮風(fēng)格二的封裝方式
風(fēng)格一
特點(diǎn)簡(jiǎn)介
將el-form, el-form-item, el-input等等完全封裝到動(dòng)態(tài)表單組件內(nèi),通過(guò)for循環(huán)來(lái)刷表單項(xiàng),外部通過(guò)傳json配置對(duì)象的方式,完成字段的動(dòng)態(tài)顯示/隱藏控制。
優(yōu)點(diǎn):
- 減少了
<el-form-item><el-input></el-input></el-form-item>
這類代碼的重復(fù)編寫 - 可以輕易的改變表單項(xiàng)的先后順序
缺點(diǎn):
- 如果所有用到表單的地方,都用這一個(gè)表單組件實(shí)現(xiàn),那么這個(gè)組件后期一定會(huì)變得非常龐大,充斥大量的邏輯控制代碼,導(dǎo)致后期難以維護(hù)
- 要想最大程度的保留原始表單的配置和事件,那就需要使用
v-bind=attrs
和v-on=evts
方式進(jìn)行配置,這相對(duì)于template語(yǔ)法來(lái)說(shuō),json對(duì)象的配置方式,就沒(méi)那么讓vue開(kāi)發(fā)者習(xí)慣了。 - 通過(guò)
v-bind=attrs
方式設(shè)置原組件的屬性,那么如果想設(shè)置一些默認(rèn)值就變得麻煩起來(lái),因?yàn)関ue2中,設(shè)置了v-bind=attrs
之后,沒(méi)法再在模板中設(shè)置默認(rèn)屬性,必須在這個(gè)封裝的組件中,在獲取到配置對(duì)象時(shí),進(jìn)行一些比較繁瑣的初始值設(shè)置。 - 如果表單model對(duì)象是通過(guò)prop傳入,那么表單內(nèi)部修改這個(gè)表單對(duì)象需要做特殊處理,來(lái)規(guī)避eslint對(duì)單項(xiàng)數(shù)據(jù)流的檢查報(bào)錯(cuò)
代碼實(shí)現(xiàn)示例
動(dòng)態(tài)表單組件
<template> <el-form :model="formModel" v-bind="elFormAttrs"> <el-form-item v-for="(formItemConfig, index) in formItemConfigArr" :key="`${formItemConfig.prop}-${index}`" :prop="formItemConfig.prop" :rules="formItemConfig.rules" > <template #label> <div class="o-custom-label">{{ formItemConfig.label }}</div> </template> <el-input v-if="formItemConfig.itemType === 'input'" v-model="formModel[formItemConfig.prop]" ></el-input> <el-select v-else-if="formItemConfig.itemType === 'select'" v-model="formModel[formItemConfig.prop]" > <template v-if="formItemConfig.optionArr"> <el-option v-for="(option, pos) in formItemConfig.optionArr" :key="`${option.value}-${pos}`" :value="option.value" :label="option.label" ></el-option> </template> </el-select> </el-form-item> </el-form> </template> <script> export default { name: "DynForm", model: { event: "change", prop: "formData", }, props: { formData: { type: Object, }, /** * prop: 唯一標(biāo)識(shí) * itemType: 表單項(xiàng)類型 * rules: 表單驗(yàn)證規(guī)則 * label: 顯示標(biāo)簽 * optionArr: 下拉值 */ formItemConfigArr: { type: Array, default: () => [], }, // el-form支持的所有屬性 elFormAttrs: { type: Object, }, }, data() { return { formModel: this.formData ? this.formData : {}, }; }, watch: { // 監(jiān)聽(tīng)formData改變,將formData的值設(shè)置給組件內(nèi)部的formModel,規(guī)避eslint對(duì)單項(xiàng)數(shù)據(jù)流的規(guī)則檢測(cè)報(bào)錯(cuò) formData: { handler(newVal) { this.formModel = newVal; }, }, }, methods: {}, }; </script> <style scoped></style>
調(diào)用動(dòng)態(tài)表單組件
<template> <div class="demo-form"> <DynForm v-model="formData" :form-item-config-arr="formItemConfigArr" :el-form-attrs="elFormAttrs" ></DynForm> </div> </template> <script> import DynForm from "@/components/form/dyn/DynForm"; export default { name: "FormTemplate3", components: { DynForm, }, props: { // input文本框數(shù)量 count: { type: Number, default: 50, }, }, data() { // 表單項(xiàng)配置 const formItemConfigArr = [ { prop: "sex", label: "成語(yǔ)故", itemType: "select", optionArr: [], }, { prop: "name", label: "姓名", itemType: "input", }, { prop: "three", label: "成語(yǔ)故事", itemType: "select", optionArr: [], }, { prop: "four", label: "一二三四五", itemType: "select", optionArr: [], }, { prop: "five", label: "一二三四五六", itemType: "select", optionArr: [], }, { prop: "six", label: "一二三四五六七", itemType: "select", optionArr: [], }, { prop: "seven", label: "一二三四五六七八", itemType: "select", optionArr: [], }, { prop: "eight", label: "一二三四五六七八九", itemType: "select", optionArr: [], }, { prop: "ten", label: "ten", itemType: "select", optionArr: [], }, { prop: "zero", label: "zero", itemType: "select", optionArr: [], }, { prop: "a", label: "hello", itemType: "select", optionArr: [], }, { prop: "b", label: "hello world", itemType: "select", optionArr: [], }, { prop: "c", label: "good idea thank", itemType: "select", optionArr: [], }, { prop: "d", label: "good configuration", itemType: "select", optionArr: [], }, { prop: "d", label: "good idea thank configuration", itemType: "select", optionArr: [], }, ]; return { elFormAttrs: { inline: true, labelWidth: "81px", }, formData: null, formItemConfigArr, }; }, created() { this.loadFormData(); this.loadSexOptions(); }, methods: { loadFormData() { setTimeout(() => { this.formData = { name: "張三", sex: 1, }; }, 1000); }, loadSexOptions() { setTimeout(() => { const item = this.formItemConfigArr.find((item) => item.prop === "sex"); if (item) { const sexOptionArr = [ { value: 1, label: "選項(xiàng)1" }, { value: 2, label: "選項(xiàng)2" }, { value: 3, label: "選項(xiàng)3" }, ]; item.optionArr = sexOptionArr; } }, 1500); }, }, }; </script> <style scoped lang="scss"> .js-validate-form ::v-deep(.is-error .o-show-data) { color: red; } .demo-form ::v-deep(.el-form-item__label) { line-height: 1.6; display: inline-flex; height: 40px; justify-items: right; justify-content: flex-end; align-items: center; } .demo-form ::v-deep(.el-form-item__label .o-custom-label) { word-break: break-word; text-overflow: ellipsis; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; /* 這里是超出幾行省略 */ overflow: hidden; } </style>
風(fēng)格二 (推薦優(yōu)先采用此風(fēng)格)
特點(diǎn)介紹
通過(guò)函數(shù)組件,以jsx語(yǔ)法對(duì)el-form-item進(jìn)行封裝,僅封裝模板代碼部分,動(dòng)態(tài)部分全部由調(diào)用者自行實(shí)現(xiàn)
優(yōu)點(diǎn):
- 能夠針對(duì)界面特點(diǎn),僅封裝不變的模板代碼部分,能夠最大程度保留template編程風(fēng)格
- 因?yàn)楸韱雾?xiàng)都是通過(guò)slot實(shí)現(xiàn),不會(huì)導(dǎo)致這個(gè)表單組件隨著應(yīng)用的場(chǎng)景增多和變得越來(lái)越復(fù)雜,復(fù)雜度會(huì)基本保持不變
缺點(diǎn):
- 需要掌握jsx語(yǔ)法(jsx僅用于封裝組件,調(diào)用這個(gè)組件使用template語(yǔ)法即可)
代碼實(shí)現(xiàn)示例
表單項(xiàng)組件封裝
<script> export default { functional: true, name: "DynFormItemsFunction", props: { formItemConfigArr: { type: Array, required: true, }, }, render(h, context) { // console.log(context); const { props, scopedSlots } = context; const { formItemConfigArr } = props; // 獲得插槽里的 vNodes const vNodes = scopedSlots.default(); return vNodes.map((node, idx) => { const formItemConfig = formItemConfigArr[idx]; return ( <el-form-item prop={formItemConfig.prop} rules={formItemConfig.rules}> <span slot="label" className="o-custom-label"> {formItemConfig.label} </span> {node} </el-form-item> ); }); }, }; </script> <style scoped> .o-custom-label { color: blue; } </style>
代碼調(diào)用示例
<template> <div> <el-form :model="formData" inline :validate-on-rule-change="false"> <!-- DynFormItemsFunction 這個(gè)組件,僅封裝了el-form-item的邏輯 --> <DynFormItemsFunction :form-item-config-arr="formItemConfigArrComp"> <!-- 注意組件的循環(huán)是在slot中進(jìn)行的,el-form-item的包裝邏輯, 交給了DynFormItemsFunction組件實(shí)現(xiàn) --> <template v-for="(formItemConfig, idx) in formItemConfigArrComp"> <el-input v-if="formItemConfig.itemType === 'input'" v-model="formData[formItemConfig.prop]" :key="idx" ></el-input> <el-select v-else-if="formItemConfig.itemType === 'select'" v-model="formData.sex" :key="idx" > <template v-if="formItemConfig.optionArr"> <el-option v-for="option in formItemConfig.optionArr" :key="option.value" :value="option.value" :label="option.label" ></el-option> </template> </el-select> </template> </DynFormItemsFunction> </el-form> </div> </template> <script> import DynFormItemsFunction from "@/components/form/dyn/DynFormItemsFunction"; export default { name: "FormTemplate", components: { DynFormItemsFunction, }, props: { count: { type: Number, default: 50, }, }, data() { /* 完整的表單配置放在data中,數(shù)據(jù)中的元素個(gè)數(shù)不會(huì)變, 動(dòng)態(tài)值也填充到formItemConfigArr對(duì)應(yīng)元素中(如性別的下拉選項(xiàng)值), formItemConfigArr只需要關(guān)注完整表單需要哪些表單項(xiàng)字段, 以及下拉選項(xiàng)數(shù)據(jù)的填充,無(wú)需考慮表單項(xiàng)字段的顯示/隱藏邏輯控制 */ const formItemConfigArr = [ { prop: "sex", label: "性別", itemType: "select", optionArr: [], }, { prop: "name", label: "姓名", itemType: "input", rules: [{ required: true, message: "該項(xiàng)必填", trigger: "change" }], }, { prop: "school", label: "學(xué)校", itemType: "input", rules: [], }, ]; // for (let i = 0; i < this.count; i++) { // formItemConfigArr.push({ // prop: "name" + i, // label: "姓名" + i, // itemType: "input", // }); // } return { formData: { name: null, sex: null, school: null, }, formItemConfigArr, }; }, computed: { /* 表單項(xiàng)的顯示/隱藏通過(guò)計(jì)算屬性實(shí)現(xiàn),可以認(rèn)為計(jì)算屬性就只關(guān)注控制哪些表單項(xiàng)需要顯示, 哪些需要隱藏,就可以了。算是一種職責(zé)分離 */ formItemConfigArrComp() { return this.formItemConfigArr.filter((item) => { if (this.formData.sex === 3 && item.prop === "name") { return null; } return item; }); }, }, created() { this.loadOptions(1500); }, methods: { loadOptions(timeout) { setTimeout(() => { const item = this.formItemConfigArr.find((item) => item.prop === "sex"); if (item) { const sexOptionArr = new Array(10) .fill(true) .map((item, idx) => ({ value: idx, label: "選項(xiàng):" + idx })); item.optionArr = sexOptionArr; } }, timeout); }, }, }; </script> <style scoped lang="scss"> .js-validate-form ::v-deep(.is-error .o-show-data) { color: red; } </style>
到此這篇關(guān)于vue2實(shí)現(xiàn)封裝動(dòng)態(tài)表單組件的文章就介紹到這了,更多相關(guān)vue封裝表單組件內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue中computed屬性和watch,methods的區(qū)別
本文主要介紹了Vue中computed屬性和watch,methods的區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05JavaScript的MVVM庫(kù)Vue.js入門學(xué)習(xí)筆記
這篇文章主要介紹了JavaScript的MVVM庫(kù)Vue.js入門學(xué)習(xí)筆記,Vue.js是一個(gè)新興的js庫(kù),主要用于實(shí)現(xiàn)響應(yīng)的數(shù)據(jù)綁定和組合的視圖組件,需要的朋友可以參考下2016-05-05uniapp微信小程序axios庫(kù)的封裝及使用詳細(xì)教程
這篇文章主要給大家介紹了關(guān)于uniapp微信小程序axios庫(kù)的封裝及使用的相關(guān)資料,Axios是一個(gè)基于promise網(wǎng)絡(luò)請(qǐng)求庫(kù),作用于node.js和瀏覽器中axios-miniprogram-adapteraxios的小程序適配器,需要的朋友可以參考下2023-08-08如何在vue3+ts項(xiàng)目中使用query和params傳參
Vue3中的路由傳參有兩種方式:query和params,下面這篇文章主要給大家介紹了關(guān)于如何在vue3+ts項(xiàng)目中使用query和params傳參的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-04-04vue打開(kāi)新窗口并實(shí)現(xiàn)傳參的圖文實(shí)例
這篇文章主要給大家介紹了關(guān)于vue打開(kāi)新窗口并實(shí)現(xiàn)傳參的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03