el-form作为包裹el-form-item层,el-form-item作为el-input,el-datepicker,el-radio等包裹成组件(可以暂时理解为父组件)
el-form
1 首先来学习一下el-form的实现,el-form具备如下a,b功能
a.承接对el-form-item加入,移除的监听。
b.使用validate包装循环el-form-item validate执行
<template>
<form class="el-form" :class="[
labelPosition ? 'el-form--label-' + labelPosition : '',
{ 'el-form--inline': inline }
]">
<slot></slot>
</form>
</template>
<script>
export default {
name: 'ElForm',
componentName: 'ElForm',
provide() {
return {
elForm: this
};
},
props: {
model: Object,
rules: Object,
labelPosition: String,
labelWidth: String,
labelSuffix: {
type: String,
default: ''
},
inline: Boolean,
inlineMessage: Boolean,
statusIcon: Boolean,
showMessage: {
type: Boolean,
default: true
},
size: String,
disabled: Boolean,
validateOnRuleChange: {
type: Boolean,
default: true
}
},
watch: {
rules() {
if (this.validateOnRuleChange) {
this.validate(() => {});
}
}
},
data() {
return {
fields: []
};
},
created() {
//监听el.form.addField事件,并将从子组件传过来的field 对象添加fields里面
this.$on('el.form.addField', (field) => {
if (field) {
this.fields.push(field);
}
});
//监听el.form.removeField事件,并将子组件传过来的field对象从fields中移除
/* istanbul ignore next */
this.$on('el.form.removeField', (field) => {
if (field.prop) {
this.fields.splice(this.fields.indexOf(field), 1);
}
});
},
methods: {
resetFields() {
if (!this.model) {
process.env.NODE_ENV !== 'production' &&
console.warn('[Element Warn][Form]model is required for resetFields to work.');
return;
}
this.fields.forEach(field => {
field.resetField();
});
},
clearValidate() {
this.fields.forEach(field => {
field.clearValidate();
});
},
//form 验证方法
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 如果没有callback 使用promise执行方法里面创造的callback
/*
new Promise(function(resolve, reject) {
callback = function(valid) {
valid ? resolve(valid) : reject(valid);
}
})
*/
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);
}
this.fields.forEach((field, index) => {
//调用el-form-item的validate方法
field.validate('', errors => {
if (errors) {
valid = false;
}
//执行callback 1.validate传过来的function 2.调用validate()没有传入callback 组件内部使用promise 创建的callback
//typeof callback=='function',必然为true, ++count 每次循环自加,和fields.length 相等则执行callback,交还给el-form的validate方法调用的回掉函数
if (typeof callback === 'function' && ++count === this.fields.length) {
callback(valid);
}
});
});
if (promise) {
return promise;
}
},
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);
}
}
};
</script>
2.学习一下el-form-item的功能
a.上报事件给el-form
mounted() {
//如果组件存在prop,则上报el.form.addField事件,并将该组件放入到父组件的fields数组里面
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
});
let rules = this.getRules();
//1.如果有检验规格rules长度存在或者required不为undefined,则监听el.form.blur,el.form.change事件
//2.this.onFieldBlur,this.onFieldChange 将执行校验
//3.el.form.blur,el.form.change事件由el-input,el-datepicker,el-radio 触发
if (rules.length || this.required !== undefined) {
this.$on('el.form.blur', this.onFieldBlur);
this.$on('el.form.change', this.onFieldChange);
}
}
},
beforeDestroy() {
//组件销毁时,触发el.form.removeField事件,el-form组件执行fields移除
//dispatch为mixin 混淆过来的,dispatch由element ui组件的EventEmitter模块提供(我将在其他文章后续补上)
this.dispatch('ElForm', 'el.form.removeField', [this]);
}
b.实现校验。this.onFieldBlur,this.onFieldChange 将执行校验
methods: {
//1.此刻校验是单个的,callback默认为noop,此刻对应this.onFieldBlur,this.onFieldChange执行,即el.form.blur事件,el.form.change事件,来源于el-input的触发
/*
2.此刻校验所有的,callback为父组件el-form调用validate方法时 function(errors){
}
*/
/*
this.fields.forEach((field, index) => {
field.validate('', errors => {
if (errors) {
valid = false;
}
if (typeof callback === 'function' && ++count === this.fields.length) {
callback(valid);
}
});
});
*/
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';
//构造一个descriptor,给asyncValidator 使用
const descriptor = {};
if (rules && rules.length > 0) {
rules.forEach(rule => {
delete rule.trigger;
});
}
descriptor[this.prop] = rules;
//构造validator
const validator = new AsyncValidator(descriptor);
//构造model模型, filedValue computed 属性
const model = {};
model[this.prop] = this.fieldValue;
//校验model是否符合rules
validator.validate(model, { firstFields: true }, (errors, fields) => {
this.validateState = !errors ? 'success' : 'error';
this.validateMessage = errors ? errors[0].message : '';
//执行callback 默认值是空方法
callback(this.validateMessage);
//$emit 上报form validate事件,在el-form 上可以@validate 很少用
//这一行代码是element ui 后来加上去的
this.elForm && this.elForm.$emit('validate', this.prop, !errors, this.validateMessage || null);
});
},
clearValidate() {
this.validateState = '';
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(/:/, '.');
}
//import { noop, getPropByPath } from 'element-ui/src/utils/util';
//getPropByPath 该方法功效是将传入的path "xxx.xxx.xx",从model中获取对应xxx.xxx.xx的值
//下面写了一个例子
let prop = getPropByPath(model, path, true);
if (Array.isArray(value)) {
this.validateDisabled = true;
prop.o[prop.k] = [].concat(this.initialValue);
} else {
this.validateDisabled = true;
prop.o[prop.k] = this.initialValue;
}
},
getRules() {
//this.form computed属性,即elForm
//表单上的传入的rules属性
let formRules = this.form.rules;
//表单域自己传入的rules
const selfRules = this.rules;
//表单域传入required
const requiredRule = this.required !== undefined ? { required: !!this.required } : [];
//从表单规格中获取到prop的rule
formRules = formRules ? getPropByPath(formRules, this.prop || '').o[this.prop || ''] : [];
//组装出所有rule
return [].concat(selfRules || formRules || []).concat(requiredRule);
},
getFilteredRule(trigger) {
//获取表单域所有rules
const rules = this.getRules();
//过滤正确的trigger[blur, change]方式rules
return rules.filter(rule => {
return !rule.trigger || rule.trigger.indexOf(trigger) !== -1;
}).map(rule => objectAssign({}, rule));
},
onFieldBlur() {
//执行validate校验方法
this.validate('blur');
},
onFieldChange() {
if (this.validateDisabled) {
this.validateDisabled = false;
return;
}
//执行validate校验方法
this.validate('change');
}
},
var model = {
name: [
{ required: true, message: '请输入活动名称', trigger: 'blur' },
{ min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
],
region: [
{ required: true, message: '请选择活动区域', trigger: 'change' }
],
date1: [
{ type: 'date', required: true, message: '请选择日期', trigger: 'change' }
],
date2: [
{ type: 'date', required: true, message: '请选择时间', trigger: 'change' }
],
type: [
{ type: 'array', required: true, message: '请至少选择一个活动性质', trigger: 'change' }
],
resource: [
{ required: true, message: '请选择活动资源', trigger: 'change' }
],
desc: [
{ required: true, message: '请填写活动形式', trigger: 'blur' }
],
obj:{
name: [
{ type: 'array', required: true, message: '请至少选择一个', trigger: 'change' },
],
haha: {
val: { required: true, message: '请填写活动形式', trigger: ["change"] }
}
}
}
function getPropByPath(obj, path, strict) {
let tempObj = obj;
path = path.replace(/\[(\w+)\]/g, '.$1');
path = path.replace(/^\./, '');
let keyArr = path.split('.');
let i = 0;
for (let len = keyArr.length; i < len - 1; ++i) {
if (!tempObj && !strict) break;
let key = keyArr[i];
if (key in tempObj) {
tempObj = tempObj[key];
} else {
if (strict) {
throw new Error('please transfer a valid prop path to form item!');
}
break;
}
}
return {
o: tempObj,
k: keyArr[i],
v: tempObj ? tempObj[keyArr[i]] : null
};
};
getPropByPath(model, "obj.name")
getPropByPath(model, "obj.haha.val")
3.el-input组件触发校验,以下截取部分(内容太长了)
<input
:tabindex="tabindex"
v-if="type !== 'textarea'"
class="el-input__inner"
v-bind="$props"
:disabled="inputDisabled"
:autocomplete="autoComplete"
:value="currentValue"
ref="input"
@input="handleInput"
@focus="handleFocus"
@blur="handleBlur"
@change="handleChange"
:aria-label="label"
>
仅截取如下2个方法
handleBlur(event) {
this.focused = false;
this.$emit('blur', event);
if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.blur', [this.currentValue]);
}
},
setCurrentValue(value) {
if (value === this.currentValue) return;
this.$nextTick(_ => {
this.resizeTextarea();
});
this.currentValue = value;
if (this.validateEvent) {
this.dispatch('ElFormItem', 'el.form.change', [value]);
}
},
以上并没有刻意去解释el-form-item里面的validate如何去做校验的,AsyncValidator 这个包可能会解决你的疑惑
自此我们可以借鉴el-form el-form-item,自定义自己的el-input 类似的组件,比如图片上传,
1.使用混淆加入mixins: [emitter], 包的地址import emitter from 'element-ui/src/mixins/emitter';
2.只需要在自定义组件中适当的实际调用this.dispatch('ElFormItem', 'el.form.change', [value]);就可以了
如果您觉得作者的文章对你有帮助,可有大赏一把,或关注作者目前创业公众号