ElementUI Form组件源码一看就会

ElementUI Form组件源码探索

在使用 elementUI 时,经常会使用到 Form 组件及其校验能力。很多时候我们不会关心其内部实现,那么今天就让我们一探其原理。

Form组件的校验功能基于策略模式及发布订阅模式实现

例如:rules传参格式是一个策略模式设计,emit、on是发布订阅模式

Form组件触发校验场景:

  • 表单组件通过失焦等事件触发校验
  • 通过调用 validate 方法进行表单校验

这里我们首先分析表单组件通过事件触发校验的逻辑。

组件事件触发校验逻辑

我们以 input 组件为示例进行分析

当我们在 rules 中配置了 trigger: 'blur' 属性时

input组件会在失焦时触发校验功能

源码 packages/input/src/index.vue 中,实现了失焦事件 @blur="handleBlur"

js 复制代码
   handleBlur(event) {
       this.focused = false;
       this.$emit('blur', event);
       if (this.validateEvent) {
         this.dispatch('ElFormItem', 'el.form.blur', [this.value]);
       }
    },

input 组件通过调用 this.dispatch('ElFormItem', 'el.form.blur', [this.value]) 触发校验逻辑

dispatch方法实现来自src/mixins/emitter中

ini 复制代码
// src/mixins/emitter源码
export default {
  methods: {
    dispatch(componentName, eventName, params) {
      var parent = this.$parent || this.$root;
      var name = parent.$options.componentName;

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

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

dispatch 方法向父级查找 ElFormItem 组件对象,通过 ElFormItem 组件对象发布事件 el.form.blur ,并传递 input 中的 value 值

packages/form/src/form-item.vue,订阅事件 this.$on('el.form.blur', this.onFieldBlur):

js 复制代码
    //el-form-item组件源码
    onFieldBlur() {
       this.validate('blur');
    },
    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';

       const descriptor = {};
       if (rules && rules.length > 0) {
         rules.forEach(rule => {
           delete rule.trigger;
         });
       }
       descriptor[this.prop] = rules;

       const validator = new AsyncValidator(descriptor);
       const model = {};

       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);
         this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null);
       });
    },

观察发现上面代码引用了 AsyncValidator 插件库进行表单的校验。

上述代码解析:

1.this.getFilteredRule 方法返回当前 ElFormItem 的 rule 参数配置。this.getFilteredRule 通过向上查找 ElForm 对象,以及 form-item 对象中prop 属性值获取对应 rule 配置。

2.descriptor:当前FormItem校验规则对象。

3.new AsyncValidator(descriptor)通过当前校验规则实例化校验对象。

4.model:FormItem 的 value 对象。

5.执行 AsyncValidator 实例对象的 validate 方法进行数据校验,并传入 model ,回调函数中包含两个参数(errors, invalidFields),errors 为 null 表示校验通过。

6.验证通过时 validateState = 'success',否则为'error',当 validateState == 'error' 时当前el-form-item组件会存在 is-error 类名,element 在css中定义了 .is-error 错误警告样式。

less 复制代码
//el-form-item组件源码
<template>
  <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,      'is-no-asterisk': elForm && elForm.hideRequiredAsterisk    },    sizeClass ? 'el-form-item--' + sizeClass : ''  ]">
...

通过调用validate方法校验

ini 复制代码
    //form.vue源码方法
     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
        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 => {
          field.validate('', (message, field) => {
            if (message) {
              valid = false;
            }
            invalidFields = objectAssign({}, invalidFields, field);
            if (typeof callback === 'function' && ++count === this.fields.length) {
              callback(valid, invalidFields);
            }
          });
        });

        if (promise) {
          return promise;
        }
      }

上述代码中 this.fields 在声明周期created(如下代码)中通过订阅方式进行 set :

javascript 复制代码
    //form.vue源码方法
    created() {
      this.$on('el.form.addField', (field) => {
        if (field) {
          this.fields.push(field);
        }
      });
      /* istanbul ignore next */
      this.$on('el.form.removeField', (field) => {
        if (field.prop) {
          this.fields.splice(this.fields.indexOf(field), 1);
        }
      });
     }

form 中的子组件 form-item在生命周期mounted中发布事件:

kotlin 复制代码
//form-item.vue源码
mounted() {
      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
        });

        this.addValidateEvents();
      }
},

所以 this.fields 是 form-item 实例对象数组。

回到上面 this.fields.forEach 中 field 即每个form-item实例对象,在循环中调用每个实例对象上的validate方法(校验逻辑同单个 ElFormItem 事件触发校验)。

结论:elementUI通过对组件与AsyncValidator的封装,利用策略模式及发布订阅模式实现了Form校验功能。

相关推荐
outstanding木槿1 小时前
react+antd的Table组件编辑单元格
前端·javascript·react.js·前端框架
好名字08212 小时前
前端取Content-Disposition中的filename字段与解码(vue)
前端·javascript·vue.js·前端框架
m0_748238784 小时前
3D架构图软件 iCraft Editor 正式发布 @icraftplayer-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
前端·react.js·前端框架
新中地GIS开发老师10 小时前
《Vue进阶教程》(12)ref的实现详细教程
前端·javascript·vue.js·arcgis·前端框架·地理信息科学·地信
Cachel wood11 小时前
Django REST framework (DRF)中的api_view和APIView权限控制
javascript·vue.js·后端·python·ui·django·前端框架
Domain-zhuo11 小时前
如何提高webpack的构建速度?
前端·webpack·前端框架·node.js·ecmascript
北京_宏哥12 小时前
python接口自动化(四十二)- 项目架构设计之大结局(超详解)
python·架构·前端框架
傻小胖12 小时前
React 脚手架使用指南
前端·react.js·前端框架
好开心331 天前
axios的使用
开发语言·前端·javascript·前端框架·html
北京_宏哥1 天前
python接口自动化(四十)- logger 日志 - 下(超详解)
python·前端框架·自动化运维