Как пузырить события в цепочке компонентов субкомпонента с помощью Vue js 2?

У меня есть приложение vue, использующее:

компонент-родительский компонент, который состоит из компонента-ребенка

внутри компонента-родителя У меня есть кнопки, когда кто-то нажимает кнопку Я хочу выпустить событие, чтобы обработать 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); line, но кажется, что это. $ emit ('itemSelected' не пройдет:

console.log('itemSelectedListener', item); // это не будет вызвано...

подсказка?

должен ли я пузырить событие из child-> parent-> Vue-instance? (Я также пробовал это, но без успеха)

Ответ 1

Существует одна проблема с вашим шаблоном 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: предпочитайте использовать кебаб-кейс для имен событий, что означает, что вы пишете с - вместо заглавных букв. Написание заглавными буквами может привести к странным ситуациям, когда ваше событие не попадает в корень.

Ответ 2

Для чего это стоит, вы можете использовать браузер API событий. Он требует немного больше сценариев, чем встроенные средства Vue, но он также помогает решить эти проблемы (и примерно столько же кода, сколько при создании "шины", как в принятом ответе).

На дочернем компоненте:

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

На родительском компоненте, в mounted части жизненного цикла:

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

Ответ 3

Это немного поздно, но вот как я это сделал:

Компонент-ребенок:

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

Компонент-родитель:

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

Ответ 4

В вашем дочернем компоненте просто используйте $emit для отправки события в $root следующим образом:

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

Затем в своем родительском компоненте (например: "Приложение") настройте слушателя в привязке к жизненному циклу, mounted на Vue, следующим образом:

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

Ответ 5

Создайте обычную директиву для всплытия и используйте ее в дочернем компоненте.

Пример директивы:

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