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

Bas*_*MHL 57 javascript vue.js vuejs2

似乎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'
})
Run Code Online (Sandbox Code Playgroud)

这个JsFiddle解决了问题https://jsfiddle.net/y5dvkqbd/4/,但通过发布两个事件:

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

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

Ber*_*ert 32

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

parent,

mounted(){
    this.$el.addEventListener("eventtriggered", () => this.performAction())
}
Run Code Online (Sandbox Code Playgroud)

否则,是的,你必须重新发射,或使用公共汽车.

注意:我在doEvent方法中添加了代码来处理IE; 该代码可以以可重用的方式提取.

  • 仅仅为了一种简单的情况而安装 vuex 是没有意义的。 (3认同)

Sly*_*nal 21

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

Vue文档(简要地)解决了非父 - 子通信部分中的这种情况.

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

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

以下是如何实现简单的全局事件总线的示例.


dig*_*out 20

如果您想灵活地向所有父母及其父母递归地广播一个事件,直到根,您可以执行以下操作:

let vm = this.$parent

while(vm) {
    vm.$emit('submit')
    vm = vm.$parent
}
Run Code Online (Sandbox Code Playgroud)


Bas*_*MHL 17

新答案(2018年11月更新)

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

this.$parent.$emit("submit", {somekey: somevalue})
Run Code Online (Sandbox Code Playgroud)

更清洁,更简单。

  • 您不希望在您的大项目中发生这种事情。你把“孩子”放在“过渡”或任何其他包装组件中,它就会崩溃,给你留下一个大问号。 (16认同)
  • 请注意,这仅在关系为子级-&gt;祖父母级时才有效。如果子级可以嵌套在任意级别的深处,则此方法“不起作用”。 (7认同)
  • @AdamOrlov 我同意,这是不好的做法。请使用 Vuex 存储处理此类事件。 (2认同)

Mic*_*ush 17

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'
})
Run Code Online (Sandbox Code Playgroud)
.child {
  padding: 10px;
  border: 1px solid #ddd;
  background: #f0f0f0
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <parent></parent>
</div>
Run Code Online (Sandbox Code Playgroud)


Sph*_*inx 14

另一种解决方案是:

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

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'
})
Run Code Online (Sandbox Code Playgroud)
<script src="https://unpkg.com/vue/dist/vue.js"></script>

<div id="app">
  <parent></parent>
</div>
Run Code Online (Sandbox Code Playgroud)


rog*_*ila 6

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

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

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

this.$parent["$parent"].$emit("myevent", { data: 123 });
Run Code Online (Sandbox Code Playgroud)

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


rol*_*oli 5

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

首先:使用以下内容创建一个 js 文件(我将其命名为 eventbus.js):

import Vue from 'vue'    
Vue.prototype.$event = new Vue()
Run Code Online (Sandbox Code Playgroud)

第二:在您的子组件中发出一个事件:

this.$event.$emit('event_name', 'data to pass')
Run Code Online (Sandbox Code Playgroud)

第三:在父级中收听该事件:

this.$event.$on('event_name', (data) => {
  console.log(data)
})
Run Code Online (Sandbox Code Playgroud)

注意:如果您不想再参加该活动,请取消注册:

this.$event.$off('event_name')
Run Code Online (Sandbox Code Playgroud)

信息:无需阅读以下个人意见

我不喜欢将 vuex 用于孙子到祖父的通信(或类似的通信级别)。

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


kub*_*mca 5

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

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


Nic*_*ick 5

重复 @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)
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

当构建一个具有远孙子组件的组件时,您不希望许多/任何组件与存储相关联,但希望根组件充当存储/事实来源,这非常有效。这类似于 Ember 的“数据向下行动向上”的理念。缺点是,如果您想监听中间每个父母的该事件,那么这是行不通的。但是,您可以使用 $propogateEmit,如上面 @kubaklam 的回答所示。

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