Vue组件中$emit失效求助:遵循文档仍无法触发自定义事件
嘿,我一眼就看出问题出在哪了——你误解了Vue自定义事件的传递范围!
问题根源
组件通过$emit触发的自定义事件,只会发送给它的直接父组件。而你的代码里,<test-emit>是写在<test-listen>的插槽<slot>里的,插槽内容的作用域属于父组件(也就是绑定在#wrap的根Vue实例),不是<test-listen>内部。所以test-emit触发的custom事件会被根实例接收,而不是你期望的test-listen组件,自然不会执行customHandler。
解决方案
这里有几种实用的修复方式,按推荐程度排序:
方法1:使用事件总线(最推荐的跨组件通信方案)
创建一个全局的空Vue实例作为事件中转站,让两个组件通过它来传递事件,完全解耦:
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <div id="wrap"> <test-listen></test-listen> </div> <script> // 创建事件总线,作为事件中转 const EventBus = new Vue(); Vue.component('test-listen', { data: () => ({}), mounted() { // 挂载时监听总线的custom事件 EventBus.$on('custom', this.customHandler); }, beforeDestroy() { // 组件销毁时移除监听,防止内存泄漏 EventBus.$off('custom', this.customHandler); }, methods: { customHandler() { console.log("works"); } }, template: ` <div class='test_listen'> <test-emit></test-emit> </div> ` }); Vue.component('test-emit',{ data: () => ({}), methods: { clickHandler() { // 通过总线触发custom事件 EventBus.$emit('custom'); } }, template : ` <div class='test_emit' @click="clickHandler"> test </div> ` }); new Vue({ el:"#wrap" }); </script> <style> .test_listen{ display:block; padding:20px; border:1px solid #000; } .test_emit{ display:block; width:100px; height:100px; background-color:#f0f; color:#fff; font-weight:700; font-size:20px; } </style>
方法2:调整组件结构,让test-emit成为test-listen的直接子组件
如果业务场景允许,把test-emit直接写在test-listen的模板里,这样它们就是直接的父子组件关系,$emit就能正常触发父组件的监听:
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <div id="wrap"> <test-listen></test-listen> </div> <script> Vue.component('test-listen', { data: () => ({}), methods: { customHandler() { console.log("works"); } }, template: ` <div class='test_listen' @custom="customHandler"> <test-emit></test-emit> </div> ` }); Vue.component('test-emit',{ data: () => ({}), methods: { clickHandler() { this.$emit('custom'); } }, template : ` <div class='test_emit' @click="clickHandler"> test </div> ` }); new Vue({ el:"#wrap" }); </script> <style> .test_listen{ display:block; padding:20px; border:1px solid #000; } .test_emit{ display:block; width:100px; height:100px; background-color:#f0f; color:#fff; font-weight:700; font-size:20px; } </style>
方法3:通过根实例转发(不推荐,耦合性高)
如果必须保留插槽结构,可以让根实例作为中间层转发事件,但这种方式会让组件依赖根实例,不利于组件复用:
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <div id="wrap"> <test-listen ref="listenComp"></test-listen> </div> <script> Vue.component('test-listen', { data: () => ({}), methods: { customHandler() { console.log("works"); } }, template: ` <div class='test_listen'> <slot></slot> </div> ` }); Vue.component('test-emit',{ data: () => ({}), methods: { clickHandler() { // 触发根实例的custom事件 this.$root.$emit('custom'); } }, template : ` <div class='test_emit' @click="clickHandler"> test </div> ` }); new Vue({ el:"#wrap", mounted() { // 根实例监听custom事件,调用test-listen的处理函数 this.$on('custom', () => { this.$refs.listenComp.customHandler(); }); } }); </script> <style> .test_listen{ display:block; padding:20px; border:1px solid #000; } .test_emit{ display:block; width:100px; height:100px; background-color:#f0f; color:#fff; font-weight:700; font-size:20px; } </style>
关键知识点总结
- Vue自定义事件是直接父子组件之间的通信方式,插槽内容的作用域属于插入插槽的父组件(也就是你写
<test-emit>的那个组件的父组件,这里是根实例)。 - 事件总线是Vue2中跨组件(非父子)通信的常用方案,Vue3中则推荐使用
mitt或状态管理工具(如Pinia)。
内容的提问来源于stack exchange,提问作者dgeare
相关产品推荐
相关产品推荐

