Vue.js passing events up to parents in components

2019-09-14 18:47发布

问题:

I have a Vue app like this:

<div id="example">

  <event-emitting-component @clicked="methodOnRootInstance"></event-emitting-component>

  <event-emitting-component-parent></event-emitting-component-parent>

  <div v-for="click in clicks">
    {{ click }}
  </div>

</div>

And here is the JS for it:

// Child
Vue.component('event-emitting-component', {
  template: '<div class="event-emitting-component" @click="$emit(\'clicked\')">Event emitter</div>'
});
// Parent
Vue.component('event-emitting-component-parent', {
  template: '<div class="event-emitting-component-parent">' +
  'A custom parent!'+
  '<event-emitting-component></event-emitting-component>' + // <-- Please note child component
  '</div>'
});
// create a root instance
new Vue({
  el: '#example',
  data: {
    clicks : []
  },
  methods : {
    methodOnRootInstance : function(){
      this.clicks.push('Element clicked');
    }
  }
})

If you want to play with it it is also here:

https://codepen.io/EightArmsHQ/pen/QgbwPG?editors=1010

When you click the top child component a click is registered on the root element. Perfect.

When the child component is nested inside a parent (the second component in the example), obviously I can't add a @clicked="methodOnRootInstance" as that method doesn't exist inside the component.

What is the best way to pass an event up through a number of nested components?

I've made a stripped back example here, but in reality some components are two or three levels deep. Is the answer (what I think it is) that inside the parent component I would have the following:

Vue.component('event-emitting-component-parent', {
  template: '<div class="event-emitting-component-parent">' +
  'A custom parent!'+
  '<event-emitting-component @clicked="passClicked"></event-emitting-component>' + // <-- Please note child component
  '</div>',
  'methods': {
    passClicked : function(){
      this.$emit('clicked')
    }
  }
});

And then in the html template add the same:

<event-emitting-component-parent @clicked="methodOnRootInstance"></event-emitting-component-parent>

I know I can get it to work like this, however it doesn't seem very elegant. I've looked in the docs and there are functions such as sync although I don't think it's what I need, I'm struggling to find the correct approach.

Bonus question: can vuex help with stuff like this?

回答1:

This is the type of problem vuex is designed to solve, however, before you consider adding an extra layer of complexity to your app, you may be able to get away with a simple global event bus, which is simply an empty Vue object to emit events onto, which can then be listened for by any component in your app, bypassing the parent-child chain:

const bus = new Vue({});

Vue.component('comp-1', {
  template: `<div>Comp 1 <button @click="emitClick">Click</button></div>`,
  methods: {
     emitClick(){
       bus.$emit('comp-1-click');
     }
  }
})


Vue.component('comp-2', {
  template: `<div><comp-1></comp-1></div>`,
})

new Vue({
  el: '#app',
  created(){
    bus.$on('comp-1-click',() => {
      console.log('Comp 1 clicked');
    });
  }
})

Here's the JSFiddle: https://jsfiddle.net/oatLhzLp/



标签: vue.js vuejs2