Vue动态表单组件封装
本文章根据Vue2版别,运用的UI库为 elementUI。源于日常开发。
运用到的Vue技巧:
- 界说v-model
- <component is=”componentName”></component> 动态组件
- v-on、v-bind、$attrs、$listeners、透传attribute
- slot插槽
1、关于组件的猜想
<my-component config="config"></my-component>
关于一个完美的组件,如上代码所示:丢入一堆config装备,组件输出我想要的页面。
那么关于一个表单组件,会需求什么呢?
根据elementUI官网中Form组件的第一个实例进行剖析。
得出结论
- 表单左边的文字每一行左边的文字:得出特点label。
- 表单组件的烘托如图中的 el-input、el-select、el-radio等组件的称号:特点component
- 表单中 el-input、el-select、el-radio-group等组件双向绑定的值: 特点key。
(el-checkbox-group 或 el-radio-group) 类的组件,尽量运用组合的模式便于双向绑定
根据最简略的需求,总结出:
// 数据模型
const config = [
{
label: "活动称号",
component: "el-input",
key: "name",
},
{
label: "活动区域",
component: "el-select",
key: "area",
},
]
// 组件运用
<my-form config="config"></my-component>
<template>
<el-form class="dynamic-form" ref="form" :model="formModel" label-width="80px">
<el-form-item v-for="(item, idx) in config" :key="idx" :label="item.label" :prop="item.key">
<el-input v-model="formModel[item.key]" v-if="item.component === 'el-input'"></el-input>
<el-select v-model="formModel[item.key]" v-if="item.component === 'el-select'"></el-select>
</el-form-item>
</el-form>
</template>
<script>
export default {
name: 'DynamicForm',
props: {
config: {
type: Array,
default: () => []
}
},
data() {
return {
formModel: {
name: '',
area: ''
}
}
}
}
</script>
收成页面烘托成果如下:
根据以上的输出成果得出以下痛点:
- props参数只读,v-model需求内部变量去处理(这里指formModel要先界说好变量)。
- 运用v-for + v-if的判别去处理,假如考虑缺少了部分组件,需求在组件内追加,繁琐。
- input、select等组件不增加参数。
- 组件与外部没有通讯。
- 表单没办法增加校验
- 数据没办法回填
…………
2、二次剖析功用
根据上一节的痛点,关于组件的需求进行二次剖析。
- 表单组件的成果要在外部方便获取。
需求在外部修正数值时回填到组件内部 (增加自界说v-model) - input、select等组件不能增加参数,
el-form-item、el-form也需求参数装备的增加。
(v-on, v-bind的批量绑定 以及透传Attributes)。 - 组件内部需求写大量的判别当时组件是什么类型,考虑缺乏是会形成后续组件的追加。(Vue动态组件解决)
由此打开第二轮装备信息数据:
特点 | 字段 |
---|---|
label | label值 |
key | 需求绑定的内容 |
slot | 具名插槽 |
component | 组件称号 |
options | 列表数据: 如 el-select、el-cascader 需求运用到的子节点数据 |
formItemAttr | 表单item事情 |
formItemEven | 表单item特点 |
componentAttr | 表单控件特点 |
componentEvent | 表单控件事情 |
3、产出
组件运用部分
<template>
<div>
<MYform style="margin:60px" label-width="100px" v-model="formData" :config="config">
<template #slider>
<el-slider v-model="formData.slot"></el-slider>
</template>
</MYform>
</div>
</template>
<script>
import MYform from "./components/myForm.vue"
export default {
name: "app",
components: {
MYform
},
data() {
return {
formData: {}
};
},
mounted() {
},
computed: {
config() {
return [
{
label: "活动称号", // label值
key: "name", // 需求绑定的内容
component: "el-input", // 组件称号
formItemAttr: {
rules: [{ required: true, message: '请输入邮箱地址', trigger: 'blur' }],
}, // 表单item特点
formItemEven: {}, // 表单item事情
componentAttr: {
clearable: true,
prefixIcon: 'el-icon-search',
}, // 表单控件特点
componentEvent: {},
},
{
label: "活动内容", // label值
key: "type", // 需求绑定的内容
component: "el-select", // 组件称号
options: [{ label: "活动1", value: 1 }, { label: "活动2", value: 2 }],
formItemAttr: {}, // 表单item特点
formItemEven: {}, // 表单item事情
componentAttr: {
clearable: true,
}, // 表单控件特点
componentEvent: {},// 表单控件事情
}, {
label: "运用slot", // label值
key: "slot", // 需求绑定的内容
slot: "slider",
formItemAttr: {}, // 表单item特点
formItemEven: {}, // 表单item事情
componentAttr: {
clearable: true,
}, // 表单控件特点
componentEvent: {},// 表单控件事情
}
]
}
},
};
</script>
最终输出的成果如下: 组件代码:
<template>
<!-- v-bind="$attrs" 用于 透传特点的接纳 v-on="$listeners" 方法的接纳 -->
<el-form
class="dynamic-form"
ref="form"
v-bind="$attrs"
v-on="$listeners"
:model="formModel">
<el-form-item
v-for="(item, idx) in config"
:key="idx" :label="item.label"
:prop="item.key"
v-bind="item.formItemAttr">
<!-- 具名插槽 -->
<slot v-if="item.slot" :name="item.slot"></slot>
<!-- 1、动态组件(用于替代遍历判别。 is直接赋值为组件的称号即可) -->
<component v-else :is="item.component"
v-model="formModel[item.key]"
v-bind="item.componentAttr"
v-on="item.componentEvent"
@change="onUpdate"
>
<!-- 单独处理 select 的options(当然也能够根据 el-select进行二次封装,去除options遍历这一块 ) -->
<template v-if="item.component === 'el-select'">
<el-option v-for="option in item.options" :key="option.value" :label="option.label" :value="option.value">
</el-option>
</template>
</component>
<!-- 默许插槽 -->
<slot></slot>
</el-form-item>
</el-form>
</template>
<script>
export default {
name: 'MyForm',
props: {
config: {
type: Array,
default: () => []
},
modelValue: {}
},
model: {
prop: 'modelValue', // v-model绑定的值,由于v-model也是props传值,所以props要存在该变量
event: 'change' // 需求在v-model绑定的值进行修正时的触发事情。
},
computed: {
},
data() {
return {
formModel: {},
}
},
watch: {
// v-model的值发生改动时,同步修正form内部的值
modelValue(val) {
// 更新formModel
this.updateFormModel(val);
},
},
created() {
// 初始化
this.initFormModel();
},
methods: {
// 初始化表单数值
initFormModel() {
let formModelInit = {};
this.config.forEach((item) => {
// el-checkbox-group 必须为数组,否则会报错
if (item.componentName === "el-checkbox-group") {
formModelInit[item.key] = [];
} else {
formModelInit[item.key] = null;
}
});
this.formModel = Object.assign(formModelInit, this.modelValue);
this.onUpdate();
},
// 更新内部值
updateFormModel(modelValue) {
// 兼并初始值和传入值
const sourceValue = modelValue ? modelValue : this.formModel;
this.formModel = Object.assign(this.formModel, sourceValue);
},
onUpdate() {
// 触发v-model的修正
this.$emit("change", this.formModel);
},
},
};
</script>
4、完毕
当然,动态组件并不是万能的,可是能够减少CV,以上代码仅仅一个概念篇的思想输出。可是在一定程度上也能够运用。
关于组件的完善,还是需求个人喜爱来处理。
比如说:
- 增加methods的方法,像element一样 this.$refs[formName].resetFields(); 去重置数据或清空校验。(当然有了v-model, 其实能够直接修正v-model的值也能够完成重置数据)。
- 对el-select进一步封装,就能够避免除写 el-options 的遍历判别。
- el-checkbox-group、el-radio-group 这类型的组件尽量不运用单个的,用group便于双向绑定。
- el-checkbox-group、el-radio-group也能够进一步的进行封装,经过增加options装备的方法,去除内部额定增加 v-for的遍历。
- 还能够增加el-row、el-col的layout布局。
- 还有增加 form-item 的显现躲藏
- 甚至还能够把数据进行抽离成JSON的格式。
……..