VueJs 2.0将大孩子的事件发送到他的祖父组件

问题描述 投票:48回答:7

似乎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/,但通过发布两个事件:

  • 一个从大孩子到中间组件
  • 然后再从中间组件发送到祖母

添加此中间事件似乎是重复且不必要的。有没有办法直接向祖父母发出我不知道的事情?

javascript vuejs2 vue.js
7个回答
26
投票

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;该代码可以以可重用的方式提取。


19
投票

是的,你是正确的事件只是从孩子到父母。他们不会走得更远,例如从孩子到祖父母。

Vue文档(简要地)解决了Non Parent-Child Communication部分中的这种情况。

一般的想法是,在祖父母组件中,您创建一个空的Vue组件,通过道具从祖父母传递给子孙。然后,祖父母监听事件,孙子孙女在“事件总线”上发出事件。

某些应用程序使用全局事件总线而不是每个组件的事件总线。使用全局事件总线意味着您需要具有唯一的事件名称或命名空间,以便事件不会在不同组件之间发生冲突。

这是how to implement a simple global event bus的一个例子。


10
投票

新答案(2018年11月更新)

我发现我们可以通过利用grand child组件中的$parent属性来实现这一点:

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

更清洁,更简单。


10
投票

另一个解决方案是在根节点上/发出:

在祖母中使用vm.$root.$emit,然后在祖先(或任何你喜欢的地方)使用vm.$root.$on

更新:有时您想在某些特定情况下禁用侦听器,请使用vm.$off(例如:lifecycle hook = beforeDestroy中的vm.$root.off('event-name'))。

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>

9
投票

Vue 2.4引入了一种使用vm.$listeners轻松地在层次结构中传递事件的方法

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

包含父范围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>

1
投票

这是我使用event bus时唯一的情况!!用于从深层嵌套子级传递数据,而不是直接父级,通信。

第一步:使用以下内容创建一个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用于祖父与祖父沟通(或类似的沟通级别)。

In vue.js for passing data from grand-parent to grand-child you can use provide/inject. But there is not something similar for the opposite thing. (grand-child to grand-parent) So I use event bus whenever I have to do that kind of communication.


1
投票

添加到BassMHL's answer,如果你想要更灵活,只是简单地向所有父母和他们的父母广播一个事件你可以做类似的事情:

let vm = this.$parent

while(vm) {
    vm.$emit('submit')
    vm = vm.$parent
}
© www.soinside.com 2019 - 2024. All rights reserved.