1.前言
这次探索源于改造element-ui
中下拉菜单的触发方式,el-dropdown
提供了两种触发方式hover
和click
。由于之前项目中有很多自定义右键菜单的操作。而element-ui
并未提供右键菜单相关的组件,于是查看el-dropdown
的源码想改造一个支持此右键操作的下拉菜单。尽管这不符合实际用户操作习惯哈,不过这只是一次改造element-ui
组件的一个尝试,改造并非止于此。
2.el-dropdown目录结构
查看dropdown.vue
和
dropdown-item.vue
中都混入了
Emitter
。
import Emitter from 'element-ui/src/mixins/emitter';
3. Emitter
在学习vue自定义事件的时候,父组件在使用子组件时,可以为子组件的自定义事件指定事件处理程序。子组件中可以通过vm.$emit(eventName, [...args])
触发自定义事件。但是vue官网的案例比较简单,案例中只涉及到一层子组件。
Emitter
定义了两个方法,扩展了自定义事件,父组件可以通过broadcast
(广播)的方式触发后代组件的自定义事件;后代组件可以通过dispatch
(派遣)的方式触发最近一级指定名称的祖先组件的自定义事件。源码如下:
// 指定后代组件名称componentName
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;
// 循环找到最近一级名称为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);
}
}
};
复制代码
4.案例
<el-dropdown trigger="click">
<span class="el-dropdown-link">
下拉菜单<i class="el-icon-arrow-down el-icon--right"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>黄金糕</el-dropdown-item>
<el-dropdown-item>狮子头</el-dropdown-item>
<el-dropdown-item>螺蛳粉</el-dropdown-item>
<el-dropdown-item>双皮奶</el-dropdown-item>
<el-dropdown-item>蚵仔煎</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
复制代码
从组件代码结果上看,el-dropdown
子组件是el-dropdown-item
,孙组件是el-dropdown-item
。查看el-dropdown
的侦听器:
watch: {
visible(val) {
this.broadcast('ElDropdownMenu', 'visible', val);
this.$emit('visible-change', val);
},
focusing(val) {
const selfDefine = this.$el.querySelector('.el-dropdown-selfdefine');
if (selfDefine) { // 自定义
if (val) {
selfDefine.className += ' focusing';
} else {
selfDefine.className = selfDefine.className.replace('focusing', '');
}
}
}
}
复制代码
this.broadcast('ElDropdownMenu', 'visible', val);
用于向后代名为ElDropdownMenu
调用名为visible
的自定义事件。
在查看dropdown-item.vue选项中的方法:
methods: {
handleClick(e) {
this.dispatch('ElDropdown', 'menu-item-click', [this.command, this]);
}
}
复制代码
this.dispatch('ElDropdown', 'menu-item-click', [this.command, this]);
触发了el-dropdown
的自定义事件menu-item-click
。
5.总结
Emitter实际上扩展了自定义事件的触发范围。父组件可以触发后代组件的自定义事件,不限于儿子组件。后代组件可以触发最近一级指定名称的祖先组件的自定义事件。其中涉及到事件处理程序的位置,通俗的讲,就是父组件干了一件事情,它可以通过广播的方式告诉后代组件作出回应,事件处理程序在后代组件中定义;而后代组件干了一件事件,可以通知某一祖先组件作出回应,事件处理程序在祖先组件中定义。