如何使用 Vue js 2 在组件子组件链上冒泡事件?

How to bubble events on a component subcomponent chain with Vue js 2?

我的 vue 应用程序使用:

component-parent 由 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 冒泡事件吗? (我也试过了,但没有成功)

您的 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 中监听该事件,然后向上传播该事件。 Fiddlehttps://jsfiddle.net/bjqwh74t/29/

第二个选项是注册一个名为 bus 的全局变量,它是一个空的 vue 实例,当您想要在非 child-parent 组件之间进行通信时,您可以使用它。 Fiddlehttps://jsfiddle.net/bjqwh74t/30/

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

文档 link 第一种情况:https://vuejs.org/v2/guide/components.html#Using-v-on-with-Custom-Events

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

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

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

子组件:

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

父组件:

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

在您的子组件中,只需使用 $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!`);
      });
    }
  }

您可以使用浏览器的事件 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));
}

创建一个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 大小写和 shorthand 事件)。

<!-- 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);
    });
  }
});

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

//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)

请注意,这两个组件彼此之间一无所知,它们实际上不需要关心事件是如何或为何引发的。

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