vue 渲染函数-表单可配置

161 阅读1分钟

为什么要使用渲染函数实现表单的可配置

如果使用模板语法实现表单的可配置,那么表单项组件中将会有大量的 v-if 的判断,就跟vue的官方用例一样。对于表单项而言可能只有标签的名称不一致等细微的地方略有不同。那我们是不是可以将这些细微的不同之处作为参数传递给组件然后去动态创建 vnode

渲染函数创建vnode

// @returns {VNode}
// @params param1 必传 param1 一个 HTML 标签名、组件选项对象,resolve 了上述任何一种的一个 async 函数。必填项 {String | Object | Function}
// @params param2 可选 一个与模板中 attribute 对应的数据对象 {Object}
// @params param3 可选 子虚拟节点 {String | Array} 
createElement(param1,param2,param3);

基于element-ui实现简单效果

image.png

LFormItem.js

// LFormItem.js
export default {
  inheritAttrs: false,
  props: {
    tag: String,
    validator: (val) => {
      return ['el-input', 'el-input-number', 'el-select', 'el-radio-group'].includes(val);
    },
    type: {
      type: String,
      validator: (val) => {
        if (this.tag === 'el-input') return ['input', 'textarea', 'number'].includes(val);
        return true;
      },
    },
    options: {
      type: Array,
      default: () => [],
    },
    value: {
      type: [String, Number, Array],
      required: true,
    },
  },
  data() {
    return { metaOptions: [] };
  },
  created() {
    this.metaOptions = this.options;
  },
  computed: {
    newVal: {
      get([value]) {
        return value;
      },
      set(val) {
        this.$emit('input', val);
        if (this.tag === 'el-select' || this.tag === 'el-radio-group' || this.tag === 'el-checkbox-group')
          this.selectChange(val);
      },
    },
  },
  methods: {
    inputChange(val) {
      this.newVal = val;
    },
    selectChange(val) {
      if (val instanceof Array) {
        this.$emit(
          'change',
          val,
          this.metaOptions.filter((meta) => val.indexOf(meta.value) !== -1)
        );
      } else {
        this.$emit(
          'change',
          val,
          this.metaOptions.find((meta) => val === meta.value)
        );
      }
    },
  },
  render(h) {
    let childVNodes = [];
    if (this.tag === 'el-select') {
      childVNodes = this.metaOptions.map((option, index) => {
        return h('el-option', { props: { key: option?.key || index, label: option.label, value: option.value } });
      });
    }
    if (this.tag === 'el-radio-group') {
      childVNodes = this.metaOptions.map((option, index) => {
        return h('el-radio', { props: { key: option?.key || index, label: option.value } }, option.label);
      });
    }
    if (this.tag === 'el-checkbox-group') {
      childVNodes = this.metaOptions.map((option, index) => {
        return h('el-checkbox', { props: { key: option?.key || index, label: option.value } }, option.label);
      });
    }
    return h(
      this.tag,
      {
        props: {
          type: this.type,
          value: this.value,
        },
        attrs: this.$attrs,
        on: { input: this.inputChange },
      },
      childVNodes
    );
  },
};

Demo.vue

<template>
  <div>
    <el-form ref="formDataRef" :model="formData" :rules="formDataRule" size="small" label-width="100px">
      <template v-for="item in formItem">
        <el-form-item :label="`${item.label}:`" :prop="item.prop" :key="item.prop">
          <l-form-item
            v-model="formData[item.prop]"
            :tag="item.tag"
            :options="item.options"
            @change="(val, entry) => changeHandle(val, entry, item.prop)"
          ></l-form-item>
          {{ item[item.prop] }}
        </el-form-item>
      </template>
    </el-form>
    <el-button @click="submit">提交</el-button>
  </div>
</template>

<script>
import LFormItem from './components/LFormItem.js';

export default {
  components: { LFormItem },
  data() {
    return {
      formItem: [
        {
          tag: 'el-input',
          type: 'input',
          label: '姓名',
          prop: 'name',
        },
        {
          tag: 'el-checkbox-group',
          label: '学科',
          prop: 'subject',
          options: [
            { label: '数学', value: 1 },
            { label: '语文', value: 2 },
          ],
        },
        {
          tag: 'el-radio-group',
          label: '性别',
          prop: 'sex',
          options: [
            { label: '男', value: 1 },
            { label: '女  ', value: 2 },
          ],
        },
      ],
      formData: {
        name: '',
        subject: [],
        sex: '',
      },
      formDataRule: {
        name: [{ required: true, message: '请输入姓名', trigger: 'blur ' }],
        subject: [{ required: true, message: '请输入学科', trigger: 'change' }],
        sex: [{ required: true, message: '请选择性别', trigger: 'change' }],
      },
    };
  },
  methods: {
    changeHandle(val, valitem, prop) {
      console.log('==changeHandle==', val, valitem, prop);
    },
    submit() {
      this.$refs.formDataRef.validate((valid) => {
        console.log('==validate==', valid);
        if (!valid) return;
        console.log(this.formData);
      });
    },
  },
};
</script>