封装Form表单组件简易版

封装组件的基本方法就是通过props和emit进行父子组件的传值和通信。利用插槽、组件等增加组件的可扩展性和复用性。

通用表单组件功能:收集数据、校验数据并提交

需求分析

  • 实现KForm
    • 指定数据、校验规则

  • KFormItem
    • 执行校验
    • 显示错误信息

  • KInput
    • 维护数据

基本表单

封装思路:

  • 整个Form表单数据绑定在el-form上::model=“form”,form就是表单的数据对象,使用provide将form数据,rules校验规则传递给后代(provide() 仅限于UI库的编写,平常不用 )。
  • 表单里面的每一项是放在el-form-item标签里面,放入我们想渲染出来的组件,如输入框,单选等;
  • 每个el-form-item中可以绑定了prop、label、rules等属性,我们可以在配置文件中配置对应属性的值进行绑定;
  • 利用 inject 将 form 表单信息注入该组件,对每一项 el-form-item 进行规则校验。

<template>
  <div class="content">
    <KForm ref="loginForm" :model="userInfo" :rules="rules">
      <KFormItem label="用户名" prop="username">
        <KInput v-model="userInfo.username" placeholder="请输入用户名"></KInput>
      </KFormItem>
      <KFormItem label="密码" prop="password">
        <KInput v-model="userInfo.password" type="password" placeholder="请输入用户名"></KInput>
      </KFormItem>
      <!-- 提交按钮 -->
      <KFormItem>
        <button @click="login">登录</button>
      </KFormItem>
    </KForm>
  </div>
</template>

<script>
import KInput from './test/KInput.vue'
import KFormItem from './test/KFormItem.vue'
import KForm from './test/KForm.vue'
export default {
  name: '',
  components: {
    KInput,
    KFormItem,
    KForm,
  },

  data() {
    return {
      userInfo: {
        username: '',
        password: '',
      },
      rules: {
        username: [
          {
            required: true,
            message: '请输入用户名名称',
          },
        ],
        password: [
          {
            required: true,
            message: '请输入密码',
          },
        ],
      },
    }
  },
  created() {},
  mounted() {},
  methods: {
    login() {
      // 调用form组件中的表单校验方法
      this.$refs['loginForm'].validate(valid => {
        if (valid) {
          alert('sumbit')
        } else {
          console.log('error submit!')
          return false
        }
      })
    },
  },
}
</script>

封装最内层KInput组件:KInput.vue

<template>
  <div>
    <!-- 自定义组件 双向绑定::value @input   -->
    <!-- v-bind="$attrs"  展开 $attrs   -->
    <input :type="type" :value="value" v-bind="$attrs" @input="onInput" />
  </div>
</template>

<script>
export default {
  name: '',
  inheritAttrs: false,
  props: {
    value: {
      type: String,
      default: '',
    },
    type: {
      type: String,
      default: 'text',
    },
  },
  data() {
    return {}
  },
  created() {},
  mounted() {},
  methods: {
    onInput(e) {
      // 派发一个 input 事件,实现数据的双向绑定
      this.$emit('input', e.target.value)

      // 通知父级执行校验
      this.$parent.$emit('valid')
    },
  },
}
</script>

注:像是在 KInput 上绑定了 placeholder="请输入用户名" 属性,但是不想再props中定义,可以使用 v-bind="$attrs" 直接展开 KInput 足尖上绑定的属性来获取 placeholder 该属性。

kformitem.vue

<template>
  <div>
    <!-- label -->
    <label v-if="label">{
   
   { label }}</label>
    <slot></slot>
    <!-- 校验信息的显示 -->
    <p v-if="error">{
   
   { error }}</p>
    <!-- <p>{
   
   { form.rules }}</p> -->
  </div>
</template>

<script>
import Schema from 'async-validator'

export default {
  name: '',
  components: {},
  inject: ['form'],
  props: {
    label: {
      type: String,
      default: '',
    },
    prop: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      error: '', // error是空说明校验通过
    }
  },
  created() {},
  mounted() {
    this.$on('valid', () => {
      this.validate()
    })
  },
  methods: {
    validate() {
      // 规则
      const rules = this.form.rules[this.prop]
      // 当前值
      const value = this.form.model[this.prop]

      // 校验描述对象
      const desc = {
        [this.prop]: rules,
      }

      // 创建 Schema 实例
      const schema = new Schema(desc)
      return schema.validate({ [this.prop]: value }, errors => {
        if (errors) {
          this.error = errors[0].message
        } else {
          this.error = ''
        }
      })
    },
  },
}
</script>

预留表单项插槽,借用 async-validator 插件完成表单校验功能,在 kformitem 组件中完成每一项表单校验的方法,但是要完成在提交时对每一个表单进行校验,就需要在 kformitem 上次层 kform 组件中循环遍历每一项的表单校验方法。 因为 schema.validate 的方法返回的实例是一个 promise ,所以可以借助 promise.all 完成表单的校验。

kform.vue

<template>
  <div>
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: '',
  components: {},
  provide() {
    return {
      form: this,
    }
  },
  props: {
    model: {
      type: Object,
      require: true,
      default: () => {
        return {}
      },
    },
    rules: {
      type: Object,
      default: () => {
        return {}
      },
    },
  },
  data() {
    return {}
  },
  created() {},
  mounted() {},
  methods: {
    validate(handle) {
      // 获取所有孩子 kformitem
      // [resultPromise]
      const tasks = this.$children
        // 过滤掉没有prop属性的孩子
        .filter(items => items.prop)
        .map(item => item.validate())

      // 统一处理 promise 结果
      Promise.all(tasks)
        .then(() => handle(true))
        .catch(() => handle(false))
    },
  },
}
</script>

因为 formitem 的子代不一定都是表单项,还有可能是按钮,按钮是没有被传递prop属性的,为了校验的是每一个有校验方法的表单项,需要过滤掉没有 prop 属性的孩子。

问题修正:

对于 input 插件中 $parent 写法问题,如果组件嵌套多层使用,使用 $parent 就不恰当了,看element-ui 源码中封装时使用了 emitter 方法的混入。

mixins: [emitter, Migrating], 
watch: {
    value(val) {
        this.$nextTick(this.resizeTextarea);
        if (this.validateEvent) {
          // this.dispatch 是emitter混入得来的
          this.dispatch('ElFormItem', 'el.form.change', [val]);
        }
    },
}

猜你喜欢

转载自blog.csdn.net/qq_43641110/article/details/128196857