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装备,组件输出我想要的页面。

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

那么关于一个表单组件,会需求什么呢?
根据elementUI官网中Form组件的第一个实例进行剖析。

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

得出结论

  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>

收成页面烘托成果如下:

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

根据以上的输出成果得出以下痛点:

  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动态组件解决)
    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>

最终输出的成果如下:

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

<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的格式。
    ……..