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

相关推荐
Minyy1120 小时前
Vue3指令(二)--v-text、v-html数据渲染,计算属性
前端·javascript·vue.js·前端框架·vue·html
.生产的驴1 天前
Vue3 加快页面加载速度 使用CDN外部库的加载 提升页面打开速度 服务器分发
运维·服务器·前端·vue.js·分布式·前端框架·vue
郝开1 天前
扩展:React 项目执行 yarn eject 后的 package.json 变化详解及参数解析
react.js·前端框架·react
junjun.chen06061 天前
【在qiankun模式下el-dropdown点击,浏览器报Failed to execute ‘getComputedStyle‘ on ‘Window‘: parameter 1 is not o
前端·javascript·前端框架
刺客-Andy2 天前
React 第四十一节Router 中 useActionData 使用方法案例以及注意事项
前端·react.js·前端框架
Zero1017132 天前
【详解pnpm、npm、yarn区别】
前端·react.js·前端框架
球球和皮皮3 天前
Babylon.js学习之路《四、Babylon.js 中的相机(Camera)与视角控制》
javascript·3d·前端框架·babylon.js
AC-PEACE3 天前
小程序初始化加载时间优化 步骤思考与总结
react.js·小程序·前端框架
Peter 谭3 天前
React Hooks 实现原理深度解析:从基础到源码级理解
前端·javascript·react.js·前端框架·ecmascript
进取星辰4 天前
25、Tailwind:魔法速记术——React 19 样式新思路
前端·react.js·前端框架