背景
最近自己封装了一个树节点选择组件,在对其做优化的时候想着能不能像el-input
那样触发el-form
的校验,并将错误提示显示在对应的el-form-item
下,如图。
分析
为了实现这一效果,首先可以el-input
为例,参考它的源码是怎么实现的。以下是el-input
的部分源码,截取了主要的相关逻辑部分,非完整源码:
源码路径:element-ui/packages/input/src/input.vue
<template>
<div>
<template v-if="type !== 'textarea'">
<input
ref="input"
@input="handleInput"
@blur="handleBlur"
@change="handleChange"
>
</template>
</div>
</template>
<script>
import emitter from 'element-ui/src/mixins/emitter';
export default {
mixins: [emitter],
data() {
return {
textareaCalcStyle: {},
hovering: false,
focused: false,
isComposing: false,
passwordVisible: false
};
},
props: {
value: [String, Number],
},
computed: {
},
watch: {
value(val) {
this.$nextTick(this.resizeTextarea);
if (this.validateEvent) {
// 在值改变的时候触发change校验
this.dispatch('ElFormItem', 'el.form.change', [val]);
}
},
},
methods: {
handleBlur(event) {
this.focused = false;
this.$emit('blur', event);
if (this.validateEvent) {
// 在失去焦点的时候触发blur校验
this.dispatch('ElFormItem', 'el.form.blur', [this.value]);
}
},
handleInput(event) {
this.$emit('input', event.target.value);
},
handleChange(event) {
this.$emit('change', event.target.value);
},
}
};
</script>
从上面的代码可以看出,el-input
组件会在blur
或change
时调用dispatch
方法,通过该方法触发相应的校验。而dispatch是ElementUI
自己封装的通信组件Emitter
中的重要组成部分(关于Emitter更多信息见下方文章末尾的拓展)。
实现
既然已经清楚了在el-input
中是怎么样实现的了,那么剩下的就是照葫芦画瓢了:
-
混入或继承
emitter
,这里以混入为例:import emitter from 'element-ui/src/mixins/emitter'; export default { mixins: [emitter] }
-
在你想要触发校验的时候调用
dispatch
,一般就是在change
或blur
的时候。// evemtName: 事件名,'el.form.blur'或'el.form.change' // params: 参数,一般主要就是值 this.dispatch('ElFormItem', eventName, params);
特别需要注意的是,
params
为一个数组,我们可以把所需的参数放在这个数组中,比如:[value, label…],这样在el-form-item
就会触发对应emit
并传出这些参数,比如:this.$emit('el.form.blur', value, label...)
。 -
最后就是设置
form Rules
,然后把自己的组件像el-input
放到el-form-item
中,这样就能看到我们想要的结果啦。<template> <el-form size="mini" :rules="rules" :model="record"> <el-form-item prop="xxx"> <TreeSelectv-model="record['xxx']"></TreeSelect> </el-form-item> </el-form> </template> <script> import TreeSelect from "@/components/Tree/TreeSelect.vue"; export default { components: {TreeSelect}, data() { return { record: { xxx: ["111"], }, treeList: [...] }; }, computed: { rules() { return { xxx: [ {required: true, type: "array", message: "xxx不能为空哦!", trigger: "change"} ], }; } } }; </script>
拓展
接下来聊聊Emitter,它是Element UI
为了解决父子组件通信问题,避免频繁使用props和emit而自行封装的一个组件通信工具。主要分为两大核心:dispatch和broadcast。
dispatch
用于向父组件逐级冒泡传递事件,主要就是通过while
循环逐级向上查找父组件,直到找到对应的父组件,并让它通过emit触发指定事件,同时传出相应参数,而在对应的父组件也存在着对‘emit触发指定事件,同时传出相应参数,而在对应的父组件也存在着对`emit触发指定事件,同时传出相应参数,而在对应的父组件也存在着对‘emit事件的处理(也就是通过
this.on‘监听∗∗同一个组件∗∗的‘this.on`监听**同一个组件**的`this.on‘监听∗∗同一个组件∗∗的‘this.emit的自定义事件触发)。就比如
el-form-item中就可能会在
mounted的时候就监听
el.form.blur和
el.form.change`的变化。
this.$on('el.form.blur', this.onFieldBlur);
this.$on('el.form.change', this.onFieldChange);
broadcast
用于向子组件传递事件,主要通过递归的方式找到目标子组件,找到对应目标后和dispatch
一样,同样通过this.$emit
触发指定事件,同时传出相应参数,然后目标子组件通过this.$on
进行监听和处理。关于broadcast
的使用可以参考一下select.vue
和select-dropdown.vue
的通信。
select组件的路径:element-ui/packages/select/src/select.vue
感谢各位的阅读,如有缺漏或错误欢迎指正,同样欢饮留言探讨,最后,附上Emitter的源码:
function broadcast(componentName, eventName, params) {
this.$children.forEach(child => {
var name = child.$options.componentName;
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
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);
}
}
};