VueJs 2.0从大孩子到他的大父母组件发出事件


87

似乎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/的问题,但是通过列举了两个事件:

  • 从孙子到中间部分
  • 然后从中间部分再次发射到祖父母

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

Answers:


64

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 class="child">' +
      '<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>


我发现这是不引入vuex / bus / root的最干净的方法。这将向上传递所有的发射功​​能。你仍然可以挖掘到儿童发出具有@ someEmit =“someEmitHandler” V系列=“$听众”前
马蒂亚斯Haugsbø

39

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


对于IE而言,行为会有所不同吗?不知道浏览器与vue有差异...
BassMHL

@BassemLhm Vue适用于IE。IE的问题不是Vue,这是DOM解决方案,您无法在IE中执行新的Event()。您必须document.createEvent()。如果需要,我可以添加IE支持。
Bert

仅在一种简单情况下安装vuex没有意义。
亚当·奥尔洛夫

@AdamOrlov我同意你的看法。
伯特

一个更简单的解决方案:stackoverflow.com/a/55650245/841591
挖掘

28

新答案(2018年11月更新)

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

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

更清洁,更简单。


15
请注意,这仅在关系为子级->祖父母级时才有效。如果子级可以嵌套任意级别,则此方法将无效
Qtax

请参阅我的答案stackoverflow.com/a/55650245/841591,以回复@Qtax的评论
digout

7
您不希望在大型项目中发生这种事情。您将“子级”放置在一个transition或其他任何包装组件中,它将破裂,使您的脑袋里有一个很大的问号。
亚当·奥尔洛夫

@AdamOrlov我同意,这是错误的做法。请使用Vuex商店处理此类事件。
Fabian von Ellerts,


26

是的,您是对的,事件只发生在孩子和父母之间。他们没有走得更远,例如从孩子到祖父母。

Vue文档(简要地)在“非父子沟通”部分解决了这种情况。

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

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

这是一个如何实现简单的全局事件总线的示例。


16

另一种解决方案将在 根节点

用途vm.$root.$emit盛大孩子,然后使用vm.$root.$on在祖先(或者任何你想)。

更新:有时您想在某些特定情况下禁用侦听器,请使用vm。$ off(例如: vm.$root.off('event-name')inside lifecycle hook = 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>


最优雅的解决方案给我。
Ovilia '18

不错的解决方案!在一个主要的服务器端应用程序(Laravel)中,它具有一些Vue组件(我不想引入Vuex层),这对我来说效果很好。我的特定问题是自定义按钮组件,该组件触发了带有表单组件的Buefy模态。提交表单后,我想禁用按钮,但是由于Modal是表单的直接父级,因此从表单发出的自定义事件不会到达按钮。
理查德

这不会销毁销毁监听器。那样行吗?如果您可以添加该逻辑,那就太好了,所以我也知道该怎么做。
mesqueeb

14

如果您想保持灵活性,并只是简单地将事件广播给所有父母,然后他们的父母从头到尾递归播放,则可以执行以下操作:

let vm = this.$parent

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

1
这是正确的答案。我创建了一个实用程序函数以在代码中的多个位置执行此操作:propertyEvent(component,eventName,value)组件= component。$ parent; }
收割

2
这是一个优雅的答案,非常易于使用和易于阅读。并且通过添加其他包装级别也不会破坏它。
院长,

4

这是我使用事件总线的唯一情况!为了将数据从深层嵌套子级传递到非直接父级,进行通信。

首先:创建一个具有以下内容的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')

INFO:无需阅读以下个人意见

我不喜欢将vuex用于子孙到祖父母的交流(或类似的交流水平)。

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


2

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

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

该解决方案是我用于实现的解决方案,但是我添加了一个附加参数targetRef来阻止在您要定位的组件上进行传播。然后,while条件将包括&& vm.$refs[targetRef]-您还需要ref在目标组件上包括该属性。 在我的用例中,我不需要一路隧穿到root,从而节省了触发事件的时间,甚至节省了几分之一秒的时间。时间
麦格劳

2

VueJS 2组件有一个 $parent包含其父组件属性。

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

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

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

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


1

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

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

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

编辑:初始vm应该设置为组件,而不是组件的父级。即let vm = this不是let vm = this.$parent


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

1
我强烈建议不要将随机属性附加到window对象,因为覆盖现有属性或与现有的第三方库很容易,这很容易。相反,使用Vue解决此问题的任何人都应该使用@roli roli的答案
HMilbradt

1
我不确定我是否完全理解或同意这一担忧。绑定到原型是一种很好的方法,但是绑定到窗口就像(如果不是更多的话)一样,是常见的,并且可能是一种更标准的处理方式。您为属性命名,因此避免命名冲突很简单。 medium.com/@amitavroy7/... stackoverflow.com/questions/15008464/... 这也是建议的解决方案杰夫方式对Laracasts使用。 laracasts.com/series/learn-vue-2-step-by-step/episodes/13
fylzero
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.