VueJs 2.0 从孙子组件发出事件到他的祖父组件

IT技术 javascript vuejs2 vue.js
2021-01-13 18:43:09

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

  • 一个从孙子到中间组件
  • 然后再次从中间组件发射到祖父

添加这个中间事件似乎是重复和不必要的。有没有办法直接发送给我不知道的祖父母?

6个回答

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>

新答案(2018 年 11 月更新)

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

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

更干净,更简单。

请注意,这仅在关系是孩子 -> 祖父母时才有效。这并没有工作,如果孩子可以嵌套任意层深。
2021-03-23 18:43:09
请参阅我的回答stackoverflow.com/a/55650245/841591以回应@Qtax 的评论
2021-03-26 18:43:09
@AdamOrlov 我同意,这是不好的做法。请使用 Vuex 商店处理这样的事件。
2021-03-29 18:43:09
真的是最好的办法!
2021-04-02 18:43:09
您不希望在您的大项目中发生这种事情。您将“子项”放在一个transition或任何其他包装器组件中,它会损坏,在您的脑海中留下一个大问号。
2021-04-08 18:43:09

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

@BassemLhm Vue 适合 IE。IE 的问题不是 Vue,而是 DOM 解决方案,您不能在 IE 中执行 new Event()。你必须document.createEvent()。如果需要,我可以添加 IE 支持。
2021-03-15 18:43:09
@AdamOrlov 我同意你的看法。
2021-03-26 18:43:09
仅仅为一个简单的案例安装 vuex 是没有意义的。
2021-04-05 18:43:09
这对 IE 有不同的表现?不知道与 vue 存在浏览器差异...
2021-04-08 18:43:09
一个更简单的解决方案:stackoverflow.com/a/55650245/841591
2021-04-12 18:43:09

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

Vue 文档(简要)在非父子通信部分解决了这种情况

一般的想法是在祖父组件中创建一个空Vue组件,组件通过 props 从祖父组件向下传递给子组件和孙组件。然后祖父母侦听事件,孙辈在该“事件总线”上发出事件。

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

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

如果你想灵活地简单地向所有父母和他们的父母递归地广播一个事件到根,你可以这样做:

let vm = this.$parent

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