Подтвердить что ты не робот

VueJs 2.0 испускает событие от великого ребенка до его родительского компонента

Кажется, что Vue.js 2.0 не выделяет события от великого ребенка к его грандиозному родительскому компоненту.

Vue.component('parent', {
  template: '<div>I am the parent - {{ action }} <child @eventtriggered="performAction"></child></div>',
  data(){
    return {
      action: 'No action'
    }
  },
  methods: {
    performAction() { this.action = 'actionDone' }
  }
})

Vue.component('child', {
  template: '<div>I am the child <grand-child></grand-child></div>'
})

Vue.component('grand-child', {
  template: '<div>I am the grand-child <button @click="doEvent">Do Event</button></div>',
  methods: {
    doEvent() { this.$emit('eventtriggered') }
  }
})

new Vue({
  el: '#app'
})

Этот JsFiddle решает проблему https://jsfiddle.net/y5dvkqbd/4/, но путем ввода двух событий:

  • От крупного до среднего компонента
  • Затем снова испускаем из среднего компонента в родительский

Добавление этого среднего события кажется повторяющимся и ненужным. Есть ли способ испустить непосредственно великого родителя, о котором я не знаю?

4b9b3361

Ответ 1

НОВЫЙ ОТВЕТ (обновление за ноябрь 2018 г.)

Я обнаружил, что мы могли бы сделать это, используя свойство $parent в компоненте grand child:

this.$parent.$emit("submit", {somekey: somevalue})

Намного чище и проще.

Ответ 2

Сообщество Vue обычно предпочитает использовать Vuex для решения этой проблемы. Изменения приводятся в состояние Vuex, и представление DOM просто вытекает из этого, устраняя необходимость в событиях во многих случаях.

Запрет на то, что переизбыток, вероятно, станет следующим лучшим выбором, и, наконец, вы можете использовать шину событий, подробно описанную в другом высокоприоритетном ответе на этот вопрос.

Ниже приведен мой первоначальный ответ на этот вопрос, и это не тот подход, который я бы принял сейчас, имея больше опыта с Vue.


Это случай, когда я могу не согласиться с выбором дизайна Vue и прибегать к DOM.

В grand-child,

methods: {
    doEvent() { 
        try {
            this.$el.dispatchEvent(new Event("eventtriggered"));
        } catch (e) {
            // handle IE not supporting Event constructor
            var evt = document.createEvent("Event");
            evt.initEvent("eventtriggered", true, false);
            this.$el.dispatchEvent(evt);
        }
    }
}

и в parent,

mounted(){
    this.$el.addEventListener("eventtriggered", () => this.performAction())
}

В противном случае, да, вы должны переизлучить или использовать шину.

Примечание. Я добавил код в методе doEvent для обработки IE; этот код может быть извлечен многоразовым способом.

Ответ 3

Да, вы правильные события переходите от ребенка к родительскому. Они не идут дальше, например. от ребенка до дедушки и бабушки.

Документация Vue (вкратце) рассматривает эту ситуацию в разделе раздел "Не родительский ребенок" .

Общая идея заключается в том, что в компоненте grandparent вы создаете пустой компонент Vue, который передается от дедушки и бабушки до детей и внуков через реквизит. Затем бабушка и дедушка прислушиваются к событиям, а внуки излучают события на этой "автобусе событий".

Некоторые приложения используют глобальную шину событий вместо шины событий для каждого компонента. Использование глобальной шины событий означает, что вам нужно будет иметь уникальные имена событий или пространство имен, чтобы события не сталкивались между различными компонентами.

Вот пример как реализовать простую глобальную шину событий.

Ответ 4

В Vue 2.4 появился способ легко передавать события вверх по иерархии с помощью vm.$listeners

С https://vuejs.org/v2/api/#vm-listeners:

Содержит родитель-Scope v-on слушателей событий (без .native модификаторов). Это может быть передано внутреннему компоненту через v-on="$listeners" - полезно при создании прозрачных компонентов оболочки.

Посмотрите фрагмент ниже, используя v-on="$listeners" в компоненте grand-child в child шаблоне:

Vue.component('parent', {
  template: '<div><p>I am the parent. The value is {{displayValue}}.</p> <child @toggle-value="toggleValue"></child></div>',
  data(){
    return {
      value: false
    }
  },
  methods: {
    toggleValue() { this.value = !this.value }
  },
  computed: {
    displayValue(){
      return (this.value ? "ON" : "OFF")
    }
  }
})

Vue.component('child', {
  template: '<div class="child"><p>I am the child. I\'m just a wrapper providing some UI.</p><grand-child v-on="$listeners"></grand-child></div>'
})

Vue.component('grand-child', {
  template: '<div><p>I am the grand-child: <button @click="emitToggleEvent">Toggle the value</button></p></div>',
  methods: {
    emitToggleEvent() { this.$emit('toggle-value') }
  }
})

new Vue({
  el: '#app'
})
.child {
  padding: 10px;
  border: 1px solid #ddd;
  background: #f0f0f0
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <parent></parent>
</div>

Ответ 5

Другое решение будет на /emit в корневом узле:

Использует vm.$root.$emit в grand-child, затем использует vm.$root.$on у предка (или где угодно).

Обновлено: иногда вы хотите отключить слушателя в некоторых конкретных ситуациях, используйте vm. $ Off (например: vm.$root.off('event-name') внутри ловушки жизненного цикла = beforeDestroy).

Vue.component('parent', {
  template: '<div><button @click="toggleEventListener()">Listener is {{eventEnable ? "On" : "Off"}}</button>I am the parent - {{ action }} <child @eventtriggered="performAction"></child></div>',
  data(){
    return {
      action: 1,
      eventEnable: false
    }
  },
  created: function () {
    this.addEventListener()
  },
  beforeDestroy: function () {
    this.removeEventListener()
  },
  methods: {
    performAction() { this.action += 1 },
    toggleEventListener: function () {
      if (this.eventEnable) {
        this.removeEventListener()
      } else {
        this.addEventListener()
      }
    },
    addEventListener: function () {
      this.$root.$on('eventtriggered1', () => {
        this.performAction()
      })
      this.eventEnable = true
    },
    removeEventListener: function () {
      this.$root.$off('eventtriggered1')
      this.eventEnable = false
    }
  }
})

Vue.component('child', {
  template: '<div>I am the child <grand-child @eventtriggered="doEvent"></grand-child></div>',
  methods: {
    doEvent() { 
    	//this.$emit('eventtriggered') 
    }
  }
})

Vue.component('grand-child', {
  template: '<div>I am the grand-child <button @click="doEvent">Emit Event</button></div>',
  methods: {
    doEvent() { this.$root.$emit('eventtriggered1') }
  }
})

new Vue({
  el: '#app'
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>

<div id="app">
  <parent></parent>
</div>

Ответ 6

Если вы хотите проявить гибкость и просто транслировать событие всем родителям и их родителям рекурсивно до корня, вы можете сделать что-то вроде:

let vm = this.$parent

while(vm) {
    vm.$emit('submit')
    vm = vm.$parent
}

Ответ 7

Это единственный случай, когда я использую шину событий ! Для передачи данных от глубоко вложенного потомка, не напрямую к родителю, общение.

Сначала: создайте файл js (я назову его eventbus.js) с таким содержимым:

import Vue from 'vue'    
Vue.prototype.$event = new Vue()

Второе: в вашем дочернем компоненте выдается событие:

this.$event.$emit('event_name', 'data to pass')

Третье: в родительском слушайте это событие:

this.$event.$on('event_name', (data) => {
  console.log(data)
})

Примечание. Если вы больше не хотите это событие, отмените его регистрацию:

this.$event.$off('event_name')

ИНФОРМАЦИЯ: Не нужно читать личное мнение ниже

Я не люблю использовать vuex для связи между внуками и прародителями (или схожим уровнем общения).

В vue.js для передачи данных от прародителя к внуку вы можете использовать предоставить/ввести. Но нет ничего похожего на противоположное. (от внука до прародителя) Поэтому я использую шину событий всякий раз, когда мне нужно сделать такое общение.

Ответ 8

Я действительно понимаю, как это происходит, создав класс, привязанный к окну, и упростив настройку широковещания/прослушивания, чтобы работать там, где вы находитесь в приложении Vue.

window.Event = new class {

    constructor() {
        this.vue = new Vue();
    }

    fire(event, data = null) {
        this.vue.$emit(event, data);
    }

    listen() {
        this.vue.$on(event, callback);  
    }

}

Теперь вы можете просто запустить/транслировать/что угодно из любого места, позвонив:

Event.fire('do-the-thing');

... и вы можете слушать родителей, бабушек и дедушек, как хотите, позвонив:

Event.listen('do-the-thing', () => {
    alert('Doing the thing!');
});

Ответ 9

Я сделал небольшой миксин на основе ответа @digout. Вы хотите поместить его перед инициализацией вашего экземпляра Vue (новое Vue...), чтобы использовать его глобально в проекте. Вы можете использовать его аналогично обычному событию.

Vue.mixin({
  methods: {
    $propagatedEmit: function (event, payload) {
      let vm = this.$parent;
      while (vm) {
        vm.$emit(event, payload);
        vm = vm.$parent;
      }
    }
  }
})