注册
web

Vue动态表单组件的一点点小想法

Vue动态表单组件封装



本文章基于Vue2版本,使用的UI库为 elementUI。源于日常开发。



使用到的Vue技巧:



  1. 定义v-model
  2. <component is="componentName"></component> 动态组件
  3. v-onv-bind$attrs$listeners、透传attribute
  4. slot插槽

1、关于组件的猜想


 <my-component config="config"></my-component>
复制代码

对于一个完美的组件,如上代码所示:丢入一堆config配置,组件输出我想要的页面。


1676516217126.png


那么对于一个表单组件,会需要什么呢?

基于elementUI官网中Form组件的第一个实例进行分析。
1676516277759.jpg

得出结论



  1. 表单左侧的文字每一行左侧的文字:得出属性label。
  2. 表单组件的渲染如图中的 el-input、el-select、el-radio等组件的名称:属性component
  3. 表单中 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>
复制代码

收获页面渲染结果如下:
1676537480111.jpg


基于以上的输出结果得出以下痛点:



  1. props参数只读,v-model需要内部变量去处理(这里指formModel要先定义好变量)。
  2. 使用v-for + v-if的判断去处理,如果思考缺少了部分组件,需要在组件内追加,繁琐。
  3. input、select等组件不添加参数。
  4. 组件与外部没有通信。
  5. 表单没办法添加校验
  6. 数据没办法回填

    ............

2、二次分析功能


基于上一节的痛点,对于组件的需求进行二次分析。



  1. 表单组件的结果要在外部方便获取。

    需要在外部修改数值时回填到组件内部 (添加自定义v-model)
  2. input、select等组件不能添加参数,

    el-form-item、el-form也需要参数配置的添加。

    (v-on, v-bind的批量绑定 以及透传Attributes)。
  3. 组件内部需要写大量的判断当前组件是什么类型,考虑不足是会造成后续组件的追加。(Vue动态组件解决)
    1676537323360.jpg

由此展开第二轮配置信息数据:















































属性字段
labellabel值
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>
复制代码

最终输出的结果如下:
动画1.gif
组件代码:


<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,以上代码只是一个概念篇的思想输出。但是在一定程度上也能够使用。

对于组件的完善,还是需要个人喜好来处理。

比如说:



  1. 添加methods的方法,像element一样 this.$refs[formName].resetFields(); 去重置数据或清空校验。(当然有了v-model, 其实可以直接修改v-model的值也可以完成重置数据)。
  2. 对el-select进一步封装,就可以避免去写 el-options 的遍历判断。
  3. el-checkbox-group、el-radio-group 这类型的组件尽量不使用单个的,用group便于双向绑定。
  4. el-checkbox-group、el-radio-group也可以进一步的进行封装,通过添加options配置的方式,去除内部额外添加 v-for的遍历。
  5. 还可以添加el-row、el-col的layout布局。
  6. 还有添加 form-item 的显示隐藏
  7. 甚至还可以把数据进行抽离成JSON的格式。

    ........

作者:半丶糖
链接:https://juejin.cn/post/7200773486796242981
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册