blog
blog copied to clipboard
【搬迁】【踩坑】Vue-eventBus异常触发事件情形
- 本文首发于
https://escawn.github.io/(已废弃)
- 发表时间2017/07/20
eventBus是一个vue实例,上面挂载了许多监听事件,通过触发相应的监听事件,可以实现不同层级的组件之间的通信。
eventBus简介
eventBus是为小型项目等组件层级不深,状态不复杂的项目提供的组件通信方案,它本质上可以看做是一个挂载了许多事件的vue实例,在所需组件中引入这个实例,以此为桥梁,触发不同层级组件中监听的事件。
基础用法
单独的eventBus文件
// 单独声明一个eventBus.js,里面仅需声明一个vue实例
// eventBus.js
import vue from "vue"
const eventBus = new Vue({})
export default eventBus
// componentA
import eventBus from './eventBus.js'
export default {
// 此处省略n行代码
methods: {
func(params) {
console.log('组件A的被触发了,' + '参数为:' + params)
}
},
mounted: function(){
this.$nextTick(function(){
eventBus.$on('message', (params) => this.func(params))
})
}
}
// componentB
import eventBus from './eventBus.js'
export default {
// 在组件B mounted时触发事件A中事件
mounted: function(){
this.$nextTick(function(){
eventBus.$emit('message', ('hhhhhh'))
})
}
}
// 组件A的message被触发了,参数为:hhhhh
组件中声明
// componentA
<script>
import Vue from 'vue'
export default {
data() {
return {
eventBus: new Vue({})
}
},
methods: {
func(params) {
console.log('组件A的被触发了,' + '参数为:' + params)
}
},
mounted: function(){
this.nextTick(function(){
this.eventBus.$on('message', (params) => this.func(params))
})
}
}
</script>
<template>
<!-- 此处省略n行代码 -->
<component-b :event-bus="eventBus"></component-b>
</template>
// componentB
export default {
props: {
eventBus: {
type: Object
}
},
// 在组件B mounted时触发事件A中事件
mounted: function(){
this.$nextTick(function(){
this.eventBus.$emit('message', ('hhhhhh'))
})
}
}
可以看到,在组件中创建eventBus的方式适用于有直接嵌套关系,并且层级不深的组件,比如父子组件(但是明显父子组件之间有更加便捷的通信方式)。一旦涉及层级较深,事件较多的情形(但是又没复杂到需要使用vuex的地步),我们会采用单文件eventBus直接引入的方式。这样的方式会带来一些隐藏的陷阱,接下来会一一提到。
异常触发事件
文档结构&场景
文档结构
.
├── componentA.vue
├── componentB.vue
└── eventBus.js
路由
routes: [
{
path: '/componentA',
name: 'componentA',
component: componentA
},
{
path: '/',
redirect: { path: '/componentA' }
},
{
path: '/componentB',
name: 'componentB',
component: componentB
}
]
行为描述
- 声明一个单文件的vue实例 eventBus
- 分别在组件A和组件B里导入eventBus
- 在组件A
mounted
时,在eventBus上$on
一个message
事件 - 在组件B内设置一个按钮,点击可以
$emit
eventBus上的message
事件 - 在两个组件的
beforeDestroy
的声明周期内打出相应讯息
期望结果
在组件A和组件B之间通过路由切换时,点击组件B内按钮,正常触发message
事件
实际结果
说明:在第二次从组件A切换到组件B时,点击按钮,出现了两次'message'事件被触发的效果,当组件A第N次切换到组件B时,点击按钮,出现了N次'message'事件被触发的效果
推测
组件的销毁不能使eventBus里的事件销毁。并且当组件再次创建时,同名事件不覆盖,会被再次注册绑定。导致看起来是多次触发了同一事件,实际上是同时触发了多个同名事件。
实验
解绑事件
做法:在组件A的beforeDestroy周期,解绑事件
beforeDestroy: function(){
eventBus.$off('message')
}
结果:达到期望结果 解决推测:组件销毁时事件没被销毁
非全局eventBus使用
做法:增加一个父组件componentParent,在父组件中声明eventBus,并以prop方式传递给组件A和组件B,在组件A销毁时销毁 结果:达到期望结果 解决推测:事件不销毁是挂载的eventBus没被销毁导致的
同名事件
做法:在组件A内绑定一个同名事件message
// componentA
mounted: function(){
this.$nextTick(function(){
eventBus.$on('message', (params) => this.func(params))
);
eventBus.$on('message', () => console.log('再次绑定了message事件'));
})
}
结果:点击按钮时,console.log
两次,说明两个事件都被触发了。
解决推测:同名事件不会被覆盖
keep-alive的影响
做法:在组件A和组件B的<router-view>
前增加<keep-alive>
标签,保持组件在路由切换时不被销毁
结果:达到期望结果
解决推测:组件销毁-创建会重复注册事件
总结
由以上实践可知
- 全局(文件式)eventBus上的事件不会随着组件的销毁而销毁
- eventBus允许同名事件存在
- 组件被重新创建会再次绑定注册事件
反思
事件解绑是必要的,之前写代码没有这方面的意识,这次踩坑发现了,写代码要严谨,考虑清楚在组件的各个声明周期内要做什么。
我也遇到了