1. form和form-item组件
先给出一个官网使用代码
<template>
<el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
<el-form-item label="密码" prop="pass">
<el-input type="password" v-model="ruleForm.pass" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="确认密码" prop="checkPass">
<el-input type="password" v-model="ruleForm.checkPass" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="年龄" prop="age">
<el-input v-model.number="ruleForm.age"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>
<el-button @click="resetForm('ruleForm')">重置</el-button>
</el-form-item>
</el-form>
</template>
<script>
export default {
data() {
var checkAge = (rule, value, callback) => {
if (!value) {
return callback(new Error('年龄不能为空'));
}
setTimeout(() => {
if (!Number.isInteger(value)) {
callback(new Error('请输入数字值'));
} else {
if (value < 18) {
callback(new Error('必须年满18岁'));
} else {
callback();
}
}
}, 1000);
};
var validatePass = (rule, value, callback) => {
if (value === '') {
callback(new Error('请输入密码'));
} else {
if (this.ruleForm.checkPass !== '') {
this.$refs.ruleForm.validateField('checkPass');
}
callback();
}
};
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'));
} else if (value !== this.ruleForm.pass) {
callback(new Error('两次输入密码不一致!'));
} else {
callback();
}
};
return {
ruleForm: {
pass: '',
checkPass: '',
age: ''
},
rules: {
pass: [
{ validator: validatePass, trigger: 'blur' }
],
checkPass: [
{ validator: validatePass2, trigger: 'blur' }
],
age: [
{ validator: checkAge, trigger: 'blur' }
]
}
};
},
methods: {
submitForm(formName) {
this.$refs[formName].validate((valid) => {
if (valid) {
alert('submit!');
} else {
console.log('error submit!!');
return false;
}
});
},
resetForm(formName) {
this.$refs[formName].resetFields();
}
}
}
</script>
结构是这样的,在加载顺序上vue是:
- 父组件先执行created,但是没有挂载
- 接着子组件执行created,然后mounted。
- 父组件接着mounted
1. form组件在created时做了什么?
做了两件事:
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) { // 表单form-item的属性
this.fields.splice(this.fields.indexOf(field), 1);
}
});
}
组件 o n 监 听 当 前 实 例 上 的 自 定 义 事 件 。 事 件 可 以 由 ‘ v m . on监听当前实例上的自定义事件。事件可以由 `vm. on监听当前实例上的自定义事件。事件可以由‘vm.emit` 触发。回调函数会接收所有传入事件触发函数的额外参数。
所以两件事:
-
监听el.form.addField自定义事件,如果这个事件被触发,就将field(子组件form-item实例对象)添加到 组件data的fields数组中
-
监听el.form.removeField自定义事件,如果事件被触发,就执行函数,函数中判断如果form-item实例对象还有prop属性。form-item的props中prop,这个属性在element官网中的可以查询到。prop: 表单域 model 字段,在使用 validate、resetFields 方法的情况下,该属性是必填的。如果在form-item组件销毁的时候会触发(比如v-if为false)
//form-item组件中的beforeDestory中做的事情。 beforeDestroy() { this.dispatch('ElForm', 'el.form.removeField', [this]); }
这里有个dispatch,是element的自己实现的,据说的是vue1.x中dispatch的阉割版。但是我也研究过vue源码
具体dispatch怎么实现的,请看本文中的element的dispatch实现原理。
2. form-item组件在mounted组件中做了什么?
form-item在created没有操作,但是在mounted做了些操作
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();
}
}
- 如果这个组件有props中的prop属性有值,那么dispatch到父组件触发el.form.addField事件。这个addField,上面有提到过,是将子组件实例对象form-item添加到form组件实例对象的fields数组中。
——————待补全------------------------------------------------------------
2. element中emitter.js
1. dispatch
使用过vuex的肯定知道vuex中也有dispatch, vuex中的disaptch是对action的派发,进入到mutation的变更。
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root; //找到组件的父组件,或者这个组件本就是根组件。
var name = parent.$options.componentName;//获取到组件的componentName的值
while (parent && (!name || name !== componentName)) {
//只要parent为true,并且有name为false,或者name和componentName不全等,就一直循环。
parent = parent.$parent; //获取上一层组件实例对象
if (parent) {//如果上层组件存在,重新将上层组件的componentName的值赋值给他
name = parent.$options.componentName;
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
}
单从dispatch的函数参数名中就可以看出是是对事件的派发,函数中使用vm.$emit触发事件。其中的componentName是element组件自定义的一个属性.
//element的form组件
export default {
name: 'ElForm',
componentName: 'ElForm'
//...
}
我们先看一个调用dispatch方法的例子
//form-item中mounted执行了这句话。this是form-item实例对象
this.dispatch('ElForm', 'el.form.addField', [this]);
那么这个dispatch一共做了两件事:
- 找到这个组件指定的父组件
- 使用vm.$emit触发这个父组件的事件(包括自定义事件)
还有就是那个while循环干了啥,为啥要循环:
- 原因就在于你要找的父组件不是直接父组件,如:
<el-form >
<el-row>
<el-col :span="24">
<el-form-item prop="checkPass">
<el-tag>标签一</el-tag>
</el-form-item>
</el-col>
</el-row>
</el-form>
如上,我el-form-item组件要触发el-form中的自定义事件,但是el-form不是直接父组件。所有while循环做了一件事情,就是层层往上找,直到找到这个父组件或者直到根组件也没有找到想要的,那么这个parent就是undefined。undefined也就不会执行下面的$emit了。