阅读 2692

拜读及分析Element源码-form表单组件篇

element from表单源码分析,涉及到input、select、checkbox、picker、radio等组件的的验证。表单的验证用到了async-validator 插件。一起来看看吧。

首先form表单组件由两部分组成

  • form: 统一管理form-item。
  • form-item:负责完成验证等。

form

结构

  <form class="el-form" :class="[
    labelPosition ? 'el-form--label-' + labelPosition : '',
    { 'el-form--inline': inline }
  ]">
    <slot></slot>
  </form>
复制代码

结构很简单,form元素包裹插槽,也就是form-item

script部分

1.方便与form-item关联,注入form实例

    // 注入组件实例
    provide() {
      return {
        elForm: this
      };
    }
复制代码

2.实例创建后,fields收集form-item的实例

    created() {
      // 监听el.form.addField事件,触发:将form-item实例push到fields
      this.$on('el.form.addField', (field) => {
        if (field) {
          this.fields.push(field);
        }
      });
      // 监听el.form.removeField事件,触发:form-item实例有prop规则属性从fields移除form-item实例
      this.$on('el.form.removeField', (field) => {
        if (field.prop) {
          this.fields.splice(this.fields.indexOf(field), 1);
        }
      });
    }
复制代码

3.关于表单验证的一些方法,form组件是做是统一管理具体执行还是在form-item中

  • 对整个表单进行验证

          // 对整个表单进行验证
          validate(callback) {
            // 没有表单数据 抛警告跳出
            if (!this.model) {
              console.warn('[Element Warn][Form]model is required for validate to work!');
              return;
            }
    
            let promise;
            // 没有callback并且浏览器支持Promise return promise
            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);
            }
            let invalidFields = {};
            // 遍历所有实例,一个个验证
            this.fields.forEach(field => {
              // 这里的validate是form-item的方法
              field.validate('', (message, field) => {
                // 如果有返回信息, 则说明验证失败
                if (message) {
                  valid = false;
                }
                // 将错误对象复制到invalidFields
                invalidFields = objectAssign({}, invalidFields, field);
                // 调callback
                if (typeof callback === 'function' && ++count === this.fields.length) {
                  callback(valid, invalidFields);
                }
              });
            });
    
            if (promise) {
              return promise;
            }
          }
    复制代码

    objectAssign方法在utils/merge 中,合并对象的方法

  • 对部分表单进行验证

          // 对部分表单验证
          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);
          }
    复制代码
  • 移除表单项的校验结果

    传入待移除的表单项的 prop 属性组成的数组,如不传则移除整个表单的校验结果

          clearValidate(props = []) {
            const fields = props.length
              ? this.fields.filter(field => props.indexOf(field.prop) > -1)
              : this.fields;
            fields.forEach(field => {
            // form-item实例的方法clearValidate(清除验证状态与提示)
              field.clearValidate();
            });
          }
    复制代码
  • 对整个表单进行重置,将所有字段值重置为初始值并移除校验结果

          resetFields() {
            // 没有表单数据 return
            if (!this.model) {
              // 环境变量,非生产环境抛警告再return
              process.env.NODE_ENV !== 'production' &&
              console.warn('[Element Warn][Form]model is required for resetFields to work.');
              return;
            }
            this.fields.forEach(field => {
              field.resetField();
            });
          }
    复制代码

4.监听验证规则

    // 监听表单验证规则
    watch: {
      rules() {
        // validateOnRuleChange未传入false则立即触发
        if (this.validateOnRuleChange) {
          // 验证
          this.validate(() => {});
        }
      }
    }
复制代码

form-item

结构

  <div class="el-form-item" :class="[{
      'el-form-item--feedback': elForm && elForm.statusIcon,
      'is-error': validateState === 'error',
      'is-validating': validateState === 'validating',
      'is-success': validateState === 'success',
      'is-required': isRequired || required
    },
    sizeClass ? 'el-form-item--' + sizeClass : ''
  ]">
    <!-- 表单域标签文本 -->
    <label :for="labelFor" class="el-form-item__label" :style="labelStyle" v-if="label || $slots.label">
      <slot name="label">{{label + form.labelSuffix}}</slot>
    </label>
    <div class="el-form-item__content" :style="contentStyle">
      <!-- 插槽接收表单验证的元素,input框单选框多选框之类的 -->
      <slot></slot>
      <!-- 验证不通过时的message -->
      <transition name="el-zoom-in-top">
        <div
          v-if="validateState === 'error' && showMessage && form.showMessage"
          class="el-form-item__error"
          :class="{
            'el-form-item__error--inline': typeof inlineMessage === 'boolean'
              ? inlineMessage
              : (elForm && elForm.inlineMessage || false)
          }"
        >
          {{validateMessage}}
        </div>
      </transition>
    </div>
  </div>
复制代码

外层div控制整体样式,内部分为两部分

  • 第一部分:可展示label或拼接统一的后缀(from的属性)
  • 第二部分:slot接收input,select等组件,以及验证不通过时的message展示

这里的变量elForm就是form组件provide的from实例

script部分

1.inject接收form实例,并向子孙后代注入form-item实例

    provide() { // 注入form-item实例
      return {
        elFormItem: this
      };
    },
    // 接收form实例
    inject: ['elForm']
复制代码

2.组件$el挂载到实例后

初始化,需要验证的让form组件收集起来,有验证规则的el.form.blur ,el.form.change 事件监听起来,等待触发验证。

    mounted() {
      // 有需要验证的表单
      if (this.prop) {
        // 向上查找form组件,并发布el.form.addField,暴露form-item实例
        // 即让form组件收集需要验证的form-item实例
        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();

        if (rules.length || this.required !== undefined) {
          // 监听el.form.blur,回调为bluer事件验证
          // 监听el.form.change事件,回调为change事件验证
          this.$on('el.form.blur', this.onFieldBlur);
          this.$on('el.form.change', this.onFieldChange);
        }
      }
    }
复制代码
  • 这里用到的dispatch方法从mixins中引入:找到指定组件,发布指定事件。

3.验证方法

      validate(trigger, callback = noop) {
        this.validateDisabled = false;
        // 符合规则的trigger
        const rules = this.getFilteredRule(trigger);
        // 没有规则也不是必填 返回true
        if ((!rules || rules.length === 0) && this.required === undefined) {
          // 执行回调
          callback();
          return true;
        }
        // 验证中
        this.validateState = 'validating';

        const descriptor = {};
        // 为了匹配AsyncValidator插件所需要的格式,需要做规则数据做一些操作
        if (rules && rules.length > 0) {
          rules.forEach(rule => {
            delete rule.trigger;
          });
        }
        // AsyncValidator需要的验证规则
        descriptor[this.prop] = rules;
        // 验证规则AsyncValidator实例对象
        const validator = new AsyncValidator(descriptor);
        const model = {};
        // AsyncValidator需要的验证数据
        model[this.prop] = this.fieldValue;
        // 验证
        validator.validate(model, { firstFields: true }, (errors, invalidFields) => {
          // 验证后的状态
          this.validateState = !errors ? 'success' : 'error';
          // 验证提示
          this.validateMessage = errors ? errors[0].message : '';
          // 执行回调
          callback(this.validateMessage, invalidFields);
          // form组件发布validate事件
          this.elForm && this.elForm.$emit('validate', this.prop, !errors);
        });
      }
复制代码

4.清空验证状态与重置表单验证方法

      // 清空验证状态及message
      clearValidate() {
        // 验证状态
        this.validateState = '';
        // 验证message
        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(/:/, '.');
        }
        // 该项表单数据
        let prop = getPropByPath(model, path, true);

        this.validateDisabled = true;
        // 重置为初始表单数据
        if (Array.isArray(value)) {
          prop.o[prop.k] = [].concat(this.initialValue);
        } else {
          prop.o[prop.k] = this.initialValue;
        }
        // 向下寻找select组件,发布fieldReset事件暴露初始表单数据
        this.broadcast('ElTimeSelect', 'fieldReset', this.initialValue);
      }
复制代码

5.监听error以及验证状态

这里的error是props接收的:若传入,状态变为error,并显示错误信息

    watch: {
      // 监听error
      error: {
        // 立即执行handler
        immediate: true, 
        handler(value) {
          // 验证状态变为error,并显示错误信息
          this.validateMessage = value;
          this.validateState = value ? 'error' : '';
        }
      },
      // 监听验证状态
      validateStatus(value) {
        this.validateState = value;
      }
    }
复制代码
6.实例销毁之前,发布form移除收集的该form-item实例
    beforeDestroy() {
      // 向上寻找form组件,发布el.form.removeField事件,暴露当前实例
      this.dispatch('ElForm', 'el.form.removeField', [this]);
    }

复制代码

form表单组件与input等组件的关联

是怎么触发from验证的呢

form在mounted时监听了el.form.blur和el.form.change事件,并指定了验证的回调函数

this.$on('el.form.blur', this.onFieldBlur);
this.$on('el.form.change', this.onFieldChange);
复制代码

以input为例

// form表单发布el.form.blur事件
this.dispatch('ElFormItem', 'el.form.blur', [this.currentValue]);
复制代码

以checkbox多选为例

// 监听value 向上寻找form组件发布el.form.change事件暴露value(数组)
this.dispatch('ElFormItem', 'el.form.change', [value]);
复制代码

等... , 这些时机触发校验

$on就是监听指定的事件并且指定回调函数,$emit就是发布某个事件并传递某些数据(可不传),当监听的事件名与发布的事件名一致就会触发监听的回调函数并且参数就是对应$emit传递的参数。

官网直通实例方法$on以及$emit

关注下面的标签,发现更多相似文章
评论