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

时间:2017-03-05 23:45:43

标签: javascript vuejs2 vue.js

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

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

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

12 个答案:

答案 0 :(得分: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;该代码可以以可重用的方式提取。

答案 1 :(得分:19)

是的,您只能将事件从孩子转到父母。他们不会走得更远,例如从孩子到祖父母。

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

一般的想法是,在祖父母组件中,您创建一个空的Vue组件,该组件通过道具从祖父母传递给子孙。祖父母随后会听取事件,孙子会在那个&#34;事件总线&#34;上发布事件。

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

以下是how to implement a simple global event bus的示例。

答案 2 :(得分:9)

另一个解决方案是:

孙子女中使用vm.$root.$emit,然后在祖先(或您想要的任何地方)使用vm.$root.$on

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

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">Do 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>

答案 3 :(得分:7)

新答案(2018年11月更新)

我发现我们实际上可以通过利用大子组件中的$parent属性来做到这一点:

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

更干净,更简单。

答案 4 :(得分:3)

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>

答案 5 :(得分:2)

VueJS 2组件具有$parent属性,其中包含其父组件。

该父组件还包括自己的$parent属性。

然后,访问“祖父母”组件就是访问“父母的父母”组件的问题:

this.$parent["$parent"].$emit("myevent", { data: 123 });

无论如何,这有点棘手,我建议使用像其他响应者所说的那样的全局状态管理器,例如Vuex或类似工具。

答案 6 :(得分: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用于子孙到祖父母的交流(或类似的交流水平)。

  

在vue.js中,可以将数据从祖父母传递给孙子女,您可以使用provide/inject。但是相反的事情没有类似的东西。 (孙子到祖父母)因此,每当需要进行此类通信时,我都会使用事件总线。

答案 7 :(得分:1)

添加到BassMHL's answer中后,如果您想变得更加灵活,并简单地向所有父母及其父母递归广播事件,则可以执行以下操作:

let vm = this.$parent

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

答案 8 :(得分:0)

我真的通过创建绑定到窗口的类并简化广播/侦听设置以在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 :(得分:0)

我根据@digout答案做了一个简短的混合。您想在Vue实例初始化(新Vue ...)之前放置它,以便在项目中全局使用它。您可以像正常事件一样使用它。

gcloud config set compute/zone <zone>

答案 10 :(得分:0)

避开@kubaklam和@digout的答案,这就是我用来避免在孙子与(可能是遥远的)祖父母之间的每个父组件上发出的东西:

{
  methods: {
    tunnelEmit (event, ...payload) {
      let vm = this.$parent
      while (vm && !vm.$listeners[event]) {
        vm = vm.$parent
      }
      if (!vm) return console.error(`no target listener for event "${event}"`)
      vm.$emit(event, ...payload)
    }
  }
}

当使用遥远的孙子代币构建组件时,您不希望将许多组件绑定到商店,而希望根组件充当事实的存储/来源,这非常有效。这类似于Ember的数据减少行为。缺点是,如果您想在中间的每个父级上监听该事件,那么它将无法正常工作。但是然后您可以像上面@kubaklam的答案一样使用$ propogateEmit。

答案 11 :(得分:0)

Vue 3 开始,根事件发生了许多根本性的变化:

$on$off$once 根方法不再存在。在某种程度上有一些东西可以代替它,因为您可以listen to root events这样做:

createApp(App, {
  // Listen for the 'expand' event
  onExpand() {
    console.log('expand')
  }
})

另一种解决方案是事件总线,但 Vue.js 文档看起来很模糊 - 从长远来看,它们会导致维护问题。您可能会得到一组不断传播的发射和事件接收器,但对其管理方式或哪些组件可能会在其他地方受到影响没有清晰或中心的概念。尽管如此,事件总线文档给出的示例是 mitttiny-emitter

但是文档明确表示他们建议按以下顺序处理这些类型的情况:

  • 道具 一种方便家长/孩子交流的解决方案。
  • 提供/注入一种让祖先与其后代交流的简单方式(尽管是批判性的,而不是相反的方式)。
  • Vuex 一种以清晰的方式处理全局状态的方法。需要注意的是,这不仅仅用于事件或通信 - Vuex 主要用于处理状态。

本质上,OP 的选择归结为使用事件总线或 Vuex。为了集中事件总线,如果状态也需要全局可用,您可以将其放置在 Vuex 中。否则,使用对其行为和位置进行严格集中控制的事件总线可能会有所帮助。