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校验功能。

相关推荐
zqx_76 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
笑非不退18 小时前
前端框架对比和选择
前端框架
老章学编程i1 天前
Vue工程化开发
开发语言·前端·javascript·vue.js·前端框架
秃头女孩y1 天前
React基础-快速梳理
前端·react.js·前端框架
Small-K1 天前
前端框架中@路径别名原理和配置
前端·webpack·typescript·前端框架·vite
sophie旭1 天前
我要拿捏 react 系列二: React 架构设计
javascript·react.js·前端框架
大道归简1 天前
2.点位管理开发(续)及设计思路——帝可得后台管理系统
java·开发语言·spring boot·spring·前端框架
liangshanbo12152 天前
将 Intersection Observer 与自定义 React Hook 结合使用
前端·react.js·前端框架
陈俊杰13 天前
流行前端框架Vue.js详细学习要点
前端框架
GoppViper3 天前
uniapp设置从右上角到左下角的三种渐变颜色
前端·前端框架·uni-app·uniapp