blog icon indicating copy to clipboard operation
blog copied to clipboard

【搬迁】【踩坑】Vue-eventBus异常触发事件情形

Open escawn opened this issue 7 years ago • 1 comments

  • 本文首发于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
  • 在组件Amounted时,在eventBus上$on一个message事件
  • 在组件B内设置一个按钮,点击可以$emiteventBus上的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允许同名事件存在
  • 组件被重新创建会再次绑定注册事件

反思

事件解绑是必要的,之前写代码没有这方面的意识,这次踩坑发现了,写代码要严谨,考虑清楚在组件的各个声明周期内要做什么。

escawn avatar Nov 15 '17 12:11 escawn

我也遇到了

myzcid avatar Mar 16 '20 13:03 myzcid