【问题标题】:How to bubble events on a component subcomponent chain with Vue js 2?如何使用 Vue js 2 在组件子组件链上冒泡事件?
【发布时间】:2017-06-21 02:44:33
【问题描述】:

我的 vue 应用程序正在使用:

component-child 组成的component-parent组件

在父组件内部我有按钮,当有人单击按钮时我想发出一个事件以便由 vue 处理并传递给另一个组件

到目前为止我做了什么:

var vm = new Vue({
    el: '#app',
    methods: {
        itemSelectedListener: function(item){
            console.log('itemSelectedListener', item);
        }
    }
});

Vue.component('component-child', {
    template: ' <span  v-on:click="chooseItem(pty )" >Button  </span>',
    methods: {
        chooseItem: function(pty){
            console.log(pty);
            this.$emit('itemSelected', {
                'priority' : pty
            });
        }
    }
});

Vue.component('component-parent', {
    template: '<component-child  v-for="q in items" ></component-child>'
});

HTML:

<component-parent v-on:itemSelected="itemSelectedListener"  ></component-parent>

它到达了我的console.log(pty); 行,但似乎this.$emit('itemSelected' 无法通过:

console.log('itemSelectedListener', item); // this is not going to be called...

提示?

我应该从 child->parent->Vue-instance 冒泡事件吗? (我也试过了,但没有成功)

【问题讨论】:

    标签: javascript vuejs2


    【解决方案1】:

    您可以使用浏览器的事件 API。它需要比 Vue 的内置内容更多的脚本,但它也可以解决这些冒泡问题(并且与创建“总线”的代码量大致相同,如已接受的答案)。

    在子组件上:

    this.$el.dispatchEvent(new CustomEvent('itemSelected', { detail: { 'priority' : pty }, bubbles: true, composed: true });
    

    在父组件上,在mounted生命周期部分:

    mounted() {
        this.$el.addEventListener('itemSelected', e => console.log('itemSelectedListener', e.detail));
    }
    

    【讨论】:

    • 我已经多次看到 Bus 建议 - 并且在 3rd 方模块中使用,但使用 Bus 对我和我的用例来说不是正确的模式,而且会很多努力让它按我想要的方式工作。这就是我想要的,现在你说的很明显,但我想不到。
    • 常规 vue 事件绑定 v-bind:itemSelected 也有效
    • 在某些情况下这是唯一的解决方案。我在 Vue 项目中使用了 Rete 框架,Rete 也使用了 Vue,但是 Rete 的 Node 作为 Vue 组件没有 $root 和 $parent,并且原生 CustomEvents 是将事件传递给我的应用程序的唯一方法。但应该是 this.$el.addEventListener('itemSelected', e => console.log('itemSelectedListener', e.detail)); addEventListener 而不是 addListener
    • 这对我有用!简单的解决方案。我使用v-on:itemSelected.native 使其工作。如果没有.native 后缀,它就无法工作。
    • 谢谢@YamiOdymel 我会修复
    【解决方案2】:

    component-parent 模板存在一个问题,因为它试图呈现多个子组件。 Vue 通常需要在组件中使用单个根 div,因此您需要将其包装在 div 或其他标签中。

    <div>
        <component-child  v-for="q in items"></component-child>
    </div>
    

    要指出的第二件事是,您从低 2 层的子组件发出一个事件,并在根目录中监听它。

    Root //but you listen to the event up here 1 level above
     Component 1 //you should listen to the event here
      Component 2 //your try to emit it from here
    

    这里有 2 个选项。从component-child 发出,在component-parent 中监听该事件,然后向上传播该事件。小提琴https://jsfiddle.net/bjqwh74t/29/

    第二个选项是注册一个名为bus 的全局变量,它是一个空的 vue 实例,当您希望在非子父组件之间进行通信时,您可以将其用于这种情况。小提琴https://jsfiddle.net/bjqwh74t/30/

    通常在父组件和子组件之间,您通过从子组件发出并使用v-on:event-name="handler" 在父组件中侦听来直接使用事件,但对于组件之间有更多级别的情况,您使用第二种方法。

    第一个案例的文档链接:https://vuejs.org/v2/guide/components.html#Using-v-on-with-Custom-Events

    第二种情况的文档链接:https://vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication

    PS:更喜欢使用 kebab-case 作为事件名称,这意味着您使用 - 而不是大写字母。用大写字母书写可能会导致奇怪的情况,即您的事件没有被捕获。

    【讨论】:

    • 第二种情况的文档链接不再存在。你有更新的链接吗?
    • 我什至会说:Vue 中的事件不像浏览器中的原生事件那样工作。 Vue 中的事件是对父级的回调,而子级中的 $emit 只是对父级回调的调用。我是对的? P.S.我翻遍了 Vue 中的所有文档,但在任何地方都没有明确说明。在我看来,他们将新手与“事件”这个词混淆了。
    • 我找到了一个很好的工作链接:vuejsdevelopers.com/2020/01/06/handling-events-vue-js“自定义事件”和“事件总线”部分
    【解决方案3】:

    我有点惊讶没有人建议使用事件总线组件。在高度解耦的系统中,有一个共享的事件总线,然后使用它来将多个断开连接的组件链接在一起发布/订阅样式是一种相当常见的模式。

    //eventbus.js
    import Vue from 'vue'
    
    export const EventBus = new Vue()
    

    一旦有了,就可以轻松地从任何地方发布活动

    // component1.js
    import { EventBus } from '@/services/eventbus'
    ...
    EventBus.$emit('do-the-things')
    

    并从其他地方聆听他们的声音。

    // component2.js
    import { EventBus } from '@/services/eventbus'
    ...
    EventBus.$on('do-the-things', this.doAllTheThings)
    

    请注意,这两个组件对彼此一无所知,它们并不真正需要关心事件是如何或为何引发的。

    这种方法有一些潜在的不良副作用。事件名称确实需要是全局唯一的,这样您就不会混淆您的应用程序。除非您执行更复杂的操作,否则您还可能通过单个对象引导每个事件。您可以对自己的应用源进行成本/收益分析,看看它是否适合您。

    【讨论】:

    • 事件总线对于更复杂的组件层次结构来说是一个很好的解决方案。但这与问题主题无关。
    • @slf 仅供参考,Cristi Jora 在他的回答中已经提到了事件总线。
    • 这是一个用作中介的全局发布/订阅,从长远来看,它可以演变成一个上帝对象,并使组件耦合到中介,或者至少是它的接口。在我看来,Vue 应该提供事件冒泡。
    【解决方案4】:

    为冒泡创建一个custome directive,并在子组件中使用它。

    示例指令:

    // Add this to main.ts when initializing Vue
    Vue.directive('bubble', {
      bind(el, { arg }, {context, componentInstance}) {
        if (!componentInstance || !context || !arg) {
          return;
        }
    
        // bubble the event to the parent
        componentInstance.$on(v, context.$emit.bind(context, arg));
      }
    });
    

    子组件可以使用该指令通过父组件发出(我切换到 kabob 案例和事件的简写)。

    <!-- template for component-parent -->
    <component-child v-bubble:item-selected  v-for="q in items"></component-child>
    
    <!-- usage of component-parent -->
    <component-parent @:item-selected="itemSelectedListener"></component-parent>
    

    包括下面我的完整绑定指令。它允许冒泡多个事件。

    <!--------------------
     * A few examples 
    --------------------->
    <!-- bubble single event -->
    <child v-bubble:click/>
    <child v-bubble:_.click/>
    <child v-bubble="'click'"/>
    <child v-bubble:any-costume-event-will-work/>
    
    <!-- bubble: click, focus, blur -->
    <child v-bubble:_.click.focus.blur/> 
    <child v-bubble="'click, focus, blur'"/>
    
    <!-- prefixed bubbling: click, focus, blur as child-click, child-focus, child-blur -->
    <child v-bubble:child.click.focus.blur/> 
    <child v-bubble:child="'click, focus, blur'"/>
    
    Vue.directive('bubble', {
            bind(el, { value, arg: prefix = '', modifiers }, {context, componentInstance}) {
      const events = value && value.trim() ? value.split(',') : Object.keys(modifiers);
        if (!events.length && prefix) {
          events.push(prefix);
          prefix = '';
        } else if(prefix) {
          prefix = prefix === '_' ? '' : prefix += '-';
        }
    
        if (!componentInstance || !context || !events.length) {
          return;
        }
    
        events.forEach((v: string) => {
          v = v.trim();
          const eventName = `${prefix}${v}`;
          const bubble = context.$emit.bind(context, eventName);
          componentInstance.$on(v, bubble);
        });
      }
    });
    

    【讨论】:

    • 我看到这个解决方案被否决了,但没有任何评论说明原因。它看起来确实很冗长,尤其是与事件总线相比,甚至与原生 JS 事件冒泡相比。虽然它可能有效,但它不是我期望的那种使用自定义指令的方式。使用此解决方案有什么优势?
    • @orionrush 这只是为已接受答案的选项 1 创建了一个简写(从子组件发出,在父组件中侦听该事件,然后向上传播该事件)。如果您查看第一个代码 sn-p,这正是正在发生的事情。更详细的版本是为了完整性而处理各种事情。本机事件冒泡不适用于自定义事件。当您有多个相同类型的组件且子组件发出相同事件时,事件总线将不起作用。该类型的所有组件都会接收事件并在只有祖先应该更新时更新。
    • 很好,感谢您的洞察力。我认为这些想法可以在答案本身中被淘汰,特别是相同类型的多个组件的子级如何发射事件将是无差别的,以及您的使用自定义指令的解决方案如何解决这个问题。可能比某些人正在寻找/理解的更先进的解决方案。
    • 我认为这应该是公认的答案。冒泡是 CustomEvent 规范的一部分。 dom.spec.whatwg.org/#interface-customevent Vue 应该允许事件冒泡
    【解决方案5】:

    在您的子组件中,只需使用$emit 将事件发送到$root,如下所示:

    v-on:click="$root.$emit('hamburger-click')"
    

    然后,在您的父组件(例如:“App”)中,在 Vue mounted 生命周期钩子中设置监听器,如下所示:

      export default {
        <snip...>
        mounted: function() {
          this.$root.$on('hamburger-click', function() {
            console.log(`Hamburger clicked!`);
          });
        }
      }
    

    【讨论】:

      【解决方案6】:

      这有点晚了,但我是这样做的:

      子组件:

      this.$root.$emit('foobar',{...});
      

      父组件:

      this.$root.$on('foobar')
      

      【讨论】:

      • 您还需要在beforeDestroy()destroy() 中调用this.$root.$off('foorbar', fn) 以避免内存泄漏,fn 变量必须与this.$root.$on('foorbar', fn) 中的值匹配,因此回调函数不能是匿名函数。
      • 注意:$on$off 自 Vue 3.0 起已弃用
      猜你喜欢
      • 2021-08-14
      • 1970-01-01
      • 1970-01-01
      • 2013-11-15
      • 2014-07-26
      • 1970-01-01
      • 2014-08-26
      • 2017-05-02
      • 1970-01-01
      相关资源
      最近更新 更多