element ui form 验证机制

el-form作为包裹el-form-item层,el-form-item作为el-input,el-datepicker,el-radio等包裹成组件(可以暂时理解为父组件)

el-form

1 首先来学习一下el-form的实现,el-form具备如下a,b功能

a.承接对el-form-item加入,移除的监听。

b.使用validate包装循环el-form-item validate执行

<template>
  <form class="el-form" :class="[
    labelPosition ? 'el-form--label-' + labelPosition : '',
    { 'el-form--inline': inline }
  ]">
    <slot></slot>
  </form>
</template>
<script>
  export default {
    name: 'ElForm',

    componentName: 'ElForm',

    provide() {
      return {
        elForm: this
      };
    },

    props: {
      model: Object,
      rules: Object,
      labelPosition: String,
      labelWidth: String,
      labelSuffix: {
        type: String,
        default: ''
      },
      inline: Boolean,
      inlineMessage: Boolean,
      statusIcon: Boolean,
      showMessage: {
        type: Boolean,
        default: true
      },
      size: String,
      disabled: Boolean,
      validateOnRuleChange: {
        type: Boolean,
        default: true
      }
    },
    watch: {
      rules() {
        if (this.validateOnRuleChange) {
          this.validate(() => {});
        }
      }
    },
    data() {
      return {
        fields: []
      };
    },
    created() {
      //监听el.form.addField事件,并将从子组件传过来的field 对象添加fields里面
      this.$on('el.form.addField', (field) => {
        if (field) {
          this.fields.push(field);
        }
      });
      //监听el.form.removeField事件,并将子组件传过来的field对象从fields中移除
      /* istanbul ignore next */
      this.$on('el.form.removeField', (field) => {
        if (field.prop) {
          this.fields.splice(this.fields.indexOf(field), 1);
        }
      });
    },
    methods: {
      resetFields() {
        if (!this.model) {
          process.env.NODE_ENV !== 'production' &&
          console.warn('[Element Warn][Form]model is required for resetFields to work.');
          return;
        }
        this.fields.forEach(field => {
          field.resetField();
        });
      },
      clearValidate() {
        this.fields.forEach(field => {
          field.clearValidate();
        });
      },
      //form 验证方法
      validate(callback) {
        if (!this.model) {
          console.warn('[Element Warn][Form]model is required for validate to work!');
          return;
        }

        let promise;
        // if no callback, return promise 如果没有callback 使用promise执行方法里面创造的callback
        /*
            new Promise(function(resolve, reject) {
                callback = function(valid) {
                    valid ? resolve(valid) : reject(valid);
                }
            })
    
       */
        if (typeof callback !== 'function' && window.Promise) {
          promise = new window.Promise((resolve, reject) => {
            callback = function(valid) {
              valid ? resolve(valid) : reject(valid);
            };
          });
        }

        let valid = true;
        let count = 0;
        // 如果需要验证的fields为空,调用验证时立刻返回callback
        if (this.fields.length === 0 && callback) {
          callback(true);
        }
        this.fields.forEach((field, index) => {
          //调用el-form-item的validate方法
          field.validate('', errors => {
            if (errors) {
              valid = false;
            }
            //执行callback 1.validate传过来的function 2.调用validate()没有传入callback 组件内部使用promise 创建的callback
            //typeof callback=='function',必然为true, ++count 每次循环自加,和fields.length 相等则执行callback,交还给el-form的validate方法调用的回掉函数
            if (typeof callback === 'function' && ++count === this.fields.length) {
              callback(valid);
            }
          });
        });

        if (promise) {
          return promise;
        }
      },
      validateField(prop, cb) {
        let field = this.fields.filter(field => field.prop === prop)[0];
        if (!field) { throw new Error('must call validateField with valid prop string!'); }

        field.validate('', cb);
      }
    }
  };
</script>

2.学习一下el-form-item的功能

a.上报事件给el-form

mounted() {
      //如果组件存在prop,则上报el.form.addField事件,并将该组件放入到父组件的fields数组里面
      if (this.prop) {
        this.dispatch('ElForm', 'el.form.addField', [this]);

        let initialValue = this.fieldValue;
        if (Array.isArray(initialValue)) {
          initialValue = [].concat(initialValue);
        }
        Object.defineProperty(this, 'initialValue', {
          value: initialValue
        });

        let rules = this.getRules();
        //1.如果有检验规格rules长度存在或者required不为undefined,则监听el.form.blur,el.form.change事件
       //2.this.onFieldBlur,this.onFieldChange 将执行校验
       //3.el.form.blur,el.form.change事件由el-input,el-datepicker,el-radio 触发
        if (rules.length || this.required !== undefined) {
          this.$on('el.form.blur', this.onFieldBlur);
          this.$on('el.form.change', this.onFieldChange);
        }
      }
    },
    beforeDestroy() {
      //组件销毁时,触发el.form.removeField事件,el-form组件执行fields移除
      //dispatch为mixin 混淆过来的,dispatch由element ui组件的EventEmitter模块提供(我将在其他文章后续补上)
      this.dispatch('ElForm', 'el.form.removeField', [this]);
    }

b.实现校验。this.onFieldBlur,this.onFieldChange 将执行校验

 methods: {
      //1.此刻校验是单个的,callback默认为noop,此刻对应this.onFieldBlur,this.onFieldChange执行,即el.form.blur事件,el.form.change事件,来源于el-input的触发
      /*
        2.此刻校验所有的,callback为父组件el-form调用validate方法时 function(errors){
          
        }
      */
      /*
         this.fields.forEach((field, index) => {
          field.validate('', errors => {
            if (errors) {
              valid = false;
            }
            if (typeof callback === 'function' && ++count === this.fields.length) {
              
              callback(valid);
            }
          });
        });
      */
      validate(trigger, callback = noop) {
        this.validateDisabled = false;
        //获取表单域的说有规格
        const rules = this.getFilteredRule(trigger);
        if ((!rules || rules.length === 0) && this.required === undefined) {
          callback();
          return true;
        }

        this.validateState = 'validating';
        
        //构造一个descriptor,给asyncValidator 使用
        const descriptor = {};
        if (rules && rules.length > 0) {
          rules.forEach(rule => {
            delete rule.trigger;
          });
        }
        descriptor[this.prop] = rules;
        
        //构造validator
        const validator = new AsyncValidator(descriptor);
        //构造model模型, filedValue computed 属性
        const model = {};
       
        model[this.prop] = this.fieldValue;
        
        //校验model是否符合rules
        validator.validate(model, { firstFields: true }, (errors, fields) => {
          this.validateState = !errors ? 'success' : 'error';
          this.validateMessage = errors ? errors[0].message : '';
          //执行callback 默认值是空方法
          callback(this.validateMessage);
          //$emit 上报form validate事件,在el-form 上可以@validate 很少用
          //这一行代码是element ui 后来加上去的
          this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null);
        });
      },
      clearValidate() {
        this.validateState = '';
        this.validateMessage = '';
        this.validateDisabled = false;
      },
      resetField() {
        this.validateState = '';
        this.validateMessage = '';

        let model = this.form.model;
        let value = this.fieldValue;
        let path = this.prop;
        if (path.indexOf(':') !== -1) {
          path = path.replace(/:/, '.');
        }
        //import { noop, getPropByPath } from 'element-ui/src/utils/util';
        //getPropByPath 该方法功效是将传入的path "xxx.xxx.xx",从model中获取对应xxx.xxx.xx的值
        //下面写了一个例子
        let prop = getPropByPath(model, path, true);

        if (Array.isArray(value)) {
          this.validateDisabled = true;
          prop.o[prop.k] = [].concat(this.initialValue);
        } else {
          this.validateDisabled = true;
          prop.o[prop.k] = this.initialValue;
        }
      },
      getRules() {
        //this.form   computed属性,即elForm
        //表单上的传入的rules属性
        let formRules = this.form.rules;
        //表单域自己传入的rules
        const selfRules = this.rules;
        //表单域传入required
        const requiredRule = this.required !== undefined ? { required: !!this.required } : [];
        //从表单规格中获取到prop的rule
        formRules = formRules ? getPropByPath(formRules, this.prop || '').o[this.prop || ''] : [];
        //组装出所有rule
        return [].concat(selfRules || formRules || []).concat(requiredRule);
      },
      getFilteredRule(trigger) {
        //获取表单域所有rules
        const rules = this.getRules();
        //过滤正确的trigger[blur, change]方式rules
        return rules.filter(rule => {
          return !rule.trigger || rule.trigger.indexOf(trigger) !== -1;
        }).map(rule => objectAssign({}, rule));
      },
      onFieldBlur() {
        //执行validate校验方法
        this.validate('blur');
      },
      onFieldChange() {
        if (this.validateDisabled) {
          this.validateDisabled = false;
          return;
        }
        //执行validate校验方法
        this.validate('change');
      }
    },
var model = {
          name: [
            { required: true, message: '请输入活动名称', trigger: 'blur' },
            { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
          ],
          region: [
            { required: true, message: '请选择活动区域', trigger: 'change' }
          ],
          date1: [
            { type: 'date', required: true, message: '请选择日期', trigger: 'change' }
          ],
          date2: [
            { type: 'date', required: true, message: '请选择时间', trigger: 'change' }
          ],
          type: [
            { type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' }
          ],
          resource: [
            { required: true, message: '请选择活动资源', trigger: 'change' }
          ],
          desc: [
            { required: true, message: '请填写活动形式', trigger: 'blur' }
          ],
          obj:{
            name: [
              {  type: 'array', required: true, message: '请至少选择一个', trigger: 'change' },
            ],
            haha: {
              val: { required: true, message: '请填写活动形式', trigger: ["change"] }
            }
          }
        }

function getPropByPath(obj, path, strict) {
  let tempObj = obj;
  path = path.replace(/\[(\w+)\]/g, '.$1');
  path = path.replace(/^\./, '');

  let keyArr = path.split('.');
  let i = 0;
  for (let len = keyArr.length; i < len - 1; ++i) {
    if (!tempObj && !strict) break;
    let key = keyArr[i];
    if (key in tempObj) {
      tempObj = tempObj[key];
    } else {
      if (strict) {
        throw new Error('please transfer a valid prop path to form item!');
      }
      break;
    }
  }
  return {
    o: tempObj,
    k: keyArr[i],
    v: tempObj ? tempObj[keyArr[i]] : null
  };
};


getPropByPath(model, "obj.name")
getPropByPath(model, "obj.haha.val")

 

3.el-input组件触发校验,以下截取部分(内容太长了)

<input
        :tabindex="tabindex"
        v-if="type !== 'textarea'"
        class="el-input__inner"
        v-bind="$props"
        :disabled="inputDisabled"
        :autocomplete="autoComplete"
        :value="currentValue"
        ref="input"
        @input="handleInput"
        @focus="handleFocus"
        @blur="handleBlur"
        @change="handleChange"
        :aria-label="label"
      >

仅截取如下2个方法

      handleBlur(event) {
        this.focused = false;
        this.$emit('blur', event);
        if (this.validateEvent) {
          this.dispatch('ElFormItem', 'el.form.blur', [this.currentValue]);
        }
      },
      setCurrentValue(value) {
        if (value === this.currentValue) return;
        this.$nextTick(_ => {
          this.resizeTextarea();
        });
        this.currentValue = value;
        if (this.validateEvent) {
          this.dispatch('ElFormItem', 'el.form.change', [value]);
        }
      },

 

以上并没有刻意去解释el-form-item里面的validate如何去做校验的,AsyncValidator 这个包可能会解决你的疑惑

自此我们可以借鉴el-form el-form-item,自定义自己的el-input 类似的组件,比如图片上传,

1.使用混淆加入mixins: [emitter], 包的地址import emitter from 'element-ui/src/mixins/emitter';

2.只需要在自定义组件中适当的实际调用this.dispatch('ElFormItem', 'el.form.change', [value]);就可以了

如果您觉得作者的文章对你有帮助,可有大赏一把,或关注作者目前创业公众号

 

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
评论

打赏作者

Hsensor

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值