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