从0开始撸一个form表单

301 阅读1分钟

首先我们先看看我们第一步要实现的表单都有那些功能吧,先看一个小小的demo

下面我们就一步一步来实现它:

基本的文件结构就是这样的,另外我们还需要一个emmiter文件 具体的代码是:

function broadcast(componentName, eventName, params) {
    this.$children.forEach(child => {
        const name = child.$options.name;

        if (name === componentName) {
            child.$emit.apply(child, [eventName].concat(params));
        } else {
            broadcast.apply(child, [componentName, eventName].concat([params]));
        }
    });
}
export default {
    methods: {
        dispatch(componentName, eventName, params) {
            let parent = this.$parent || this.$root;
            let name = parent.$options.name;

            while (parent && (!name || name !== componentName)) {
                parent = parent.$parent;

                if (parent) {
                    name = parent.$options.name;
                }
            }
            if (parent) {
                parent.$emit.apply(parent, [eventName].concat(params));
            }
        },
        broadcast(componentName, eventName, params) {
            broadcast.call(this, componentName, eventName, params);
        }
    }
};

只需要放在mixins里面就行了 到时候会用到

input文件里面我们就实现一个最简单的input组件

<template>
  <input type="text" @blur="handleInputBlur" @input="handleInput" :value="currentValue">
</template>

<script>
  import Emitter from '@/mixins/emitter'
  export default {
    name: 'Input',
    components: {

    },
    props: {
      value: '',
    },
    mixins: [ Emitter ],
    data() {
      return{
        currentValue: ''
      }
    },
    computed: {

    },
    watch: {
      value(val){
        this.currentValue = val
      }
    },
    mounted() {

    },
    methods: {
      // 这里我们只是用到了input里面的blue和input方法

      handleInputBlur(e){
        // 这里是v-model的语法糖
        this.$emit('input', this.currentValue)

        //这里我们用mixins里面的dispath方法  来触发父组件方法
        this.dispatch('pFormItem', 'on-input-blur', this.currentValue)
      },

      handleInput(e){
        let value = e.target.value
        this.$emit('input', value)

         //这里我们用mixins里面的dispath方法  来触发父组件方法
        this.dispatch('pFormItem', 'on-input-change', value)
      }
    }
  }
</script>

</style>

下面再来看看form-item中都有那些内容吧

<template>
  <div class="form-item">
    <span :class="[isRequired ? 'form-item-require' : '', 'fll']" :style="{width: form.labelWidth}">{{label}}</span>
    <!-- 默认的插槽 -->
    <slot class="fll"></slot>
    <!-- 错误提示信息 -->
    <div v-if="validateState === 'error'" class="form-item-error-message" :style="{marginLeft: form.labelWidth}">{{validateMessage}}</div>
  </div>
</template>

<script>
  import AsyncValidator from 'async-validator';
  import Emitter from '@/mixins/emitter'
  export default {
    name: 'pFormItem',
    components: {

    },
    props: {
      prop: String,
      label: String
    },
    inject: ['form'],  //获取到父组件中注入的当前实例
    mixins: [ Emitter ],
    data() {
      return{
        isRequired: false,  //是否是必填项
        initialValue: '',
        validateState: '',  //校验状态
        validateMessage: '' //校验信息
      }
    },
    computed: {
      fieldValue(){
        // 获取表单的值
        return this.form.model[this.prop]
      }
    },
    mounted() {
      if(this.prop){
        // 校验的时候我们主要是通过prop来进行判断的    有prop则进行校验

        // 设置初始值,以便在重置时恢复默认值
        this.initialValue = this.fieldValue
        this.setRules()
      }
    },
    methods: {
      setRules(){
        let rules = this.getRules()
        rules.every(rule => {
          // 过滤required
          this.isRequired = rule.required
        })
        // 传入prop需要验证
        if(this.prop){
          // 如果需要验证  则把当前form-item实例缓存到form中
          this.dispatch('pForm', 'on-form-item-add', this)
        }

        // 挂载的两个blur和change事件
        this.$on('on-input-blur', this.onFieldBlur)
        this.$on('on-input-change', this.onFieldChange)
      },

      onFieldBlur(){
        this.validate('blur')
      },

      onFieldChange(){
        this.validate('change')
      },

      // 获取当前rules
      getRules(){
        let formRules = this.form.rules
        formRules = formRules ? formRules[this.prop] : []
        return [].concat(formRules || [])
      },

      getFilterRule(trigger){
        const rules = this.getRules()
        return rules.filter(rule => !rule.trigger || rule.trigger.indexOf(trigger) !== -1)
      },

      validate(trigger, callback = function(){}){
        // 这里主要是通过rules来判断是否需要进行校验
        let rules = this.getFilterRule(trigger)
        if(!rules || rules.length === 0){
          return true
        }

        this.validateState = 'validating'
        let descriptor = {}
        let source = {}

        descriptor[this.prop] = this.form.rules[this.prop]
        source[this.prop] = this.fieldValue

        // 这里主要是使用async-validator来进行校验的
        // descriptor指的是当前校验规则
        const validator = new AsyncValidator(descriptor)

        validator.validate(source, { firstFields: true },  error => {
          // 通过error来对校验结果进行处理
          this.validateState = error ? 'error' : 'success'
          this.validateMessage = error ? error[0].message : ''
          callback(this.validateMessage)
        })
      },

      // 重置当前表单
      resetField(){
        this.form.model[this.prop] = ''
      }
    }
  }
</script>

这里我们只在form-item进行了表单的验证和重置当前表单两个操作

form组件

<template>
  <div class="form">
    <slot></slot>
  </div>
</template>

<script>
import { resolve, reject } from 'q';
  export default {
    name: 'pForm',
    components: {

    },
    props: {
      // 数据
      model: {
        type: Object
      },
      
      // rules校验规则
      rules: {
        type: Object
      },

      // label的width
      labelWidth: {
        type: String
      }
    },

    provide(){
      return {
        form: this    //在父组件注入当前实例
      }
    },
    
    data() {
      return{
        fields: []  //缓存form-item的数组
      }
    },
    computed: {

    },
    created() {
      this.$on('on-form-item-add', (fields) => {

        //把form-item放到fields中
        if(fields) this.fields.push(fields)
      })
    },
    methods: {

      // 重置当前表单  这里是循环form-item并调用里面的resetField方法
      resetFields(){
        this.fields.forEach(field => {
          field.resetField()
        });
      },

      // 整个表单的验证
      validate(callback){
        // 支持promise
        return new Promise((resolve, reject) => {
          let valid = true
          let count = 0

          // 这里也是循环form-item实例使用里面的validate方法
          this.fields.forEach(field => {
            field.validate('', errors => {
            
              if(errors){
                valid = false
              }
              // 这里是判断循环是否结束
              if(++count === this.fields.length){
                resolve(valid)

                // form表单的验证结果即支持promise也支持callback
                if(typeof callback === 'function'){
                  callback(valid)
                }
              }
            })
          })
        })
      }  
    }
  }
</script>

到此我们的基本的form表单验证基本上完成了。

下面我们说一下具体的实现思路:我们首先在form中provide当前实例,然后在form-item中获取实例中的数据,通过input的blur和change事件来触发校验的方法,并且我们组件加载的时候把需要验证的实例缓存到form中,方便我们以后调用form-item中的方法, 基本的思路就是这样的。

如果感觉有用就start一下吧 😄😄

使用中有什么问题,或者不足之处可以随时联系我!!