Thursday, November 23, 2017

Understanding Three Cases of Components Communication in Vue 2.0

The way components communicate, in Vue 2.0, is through props and custom events. And with Vue 2.0 release, it has actually gotten simpler. So, in this article, I’d like to show you the different cases in which components communicate, and how to handle each one properly.

Three cases of components communication:
1. From parent to direct children, no custom events required; just use props.
2. From a child to its direct parent, use $emit(eventName) in children and the directive v-on:event-name in the parent.
3. Non Parent-Child communication i.e global component communication (from any component to any component), use the global event bus. Or use Vuex for more complex cases.

1. Parent-Child communication

It's called parent-child communication because they are directly related to each other. They inevitably need to communicate to one another: the parent may need to pass data down to the child, and the child may need to inform the parent of something that happened in the child.

In Vue, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events.

Pssing data with props
Data can be passed down to child components using props. A prop is a custom attribute for passing information from parent components. A child component needs to explicitly declare the props it expects to receive using the props option:
 <template>
  <div class="message">
    {{ message }}
  </div>
 </template>

 <script>
 export default {
   name: "child",
   props: ['message'] 
 }
 </script>
Then we can pass a plain string to child component from parent like so:
 <child message="hello!"></child>

2. Child-Parent Communication

We have learned that the parent can pass data down to the child using props, but how do we communicate back to the parent when something happens? This is where Vue's custom event system comes in.

To accomplish this kind of communication, you have to do two things:
1. First, Fire an event from the child component using $emit(eventName, [data]).
2. Listen to it on the parent using the v-on:event-name directly in the template where the child component is used.

Here’s a quick example:
Let’s say you’ve built your own custom dropdown component that contains a bunch of options which you pass from the parent. When an option is selected in that dropdown, it should emit an event to its parent, so it can do its stuff.

Here’s what happens when an option is selected in Dropdown.vue:
data () {
 return {
   selectedOption: null
 }
},
methods: {
  selected () {
    // assume 'this.selectedOption' is defined in the data object
    this.$emit('option-select', this.selectedOption)
  }
}
Now in the parent component — let’s call it NavBar.vue — we’ll have something like this (note that I’m using the shorthand for v-on which is @):
 <template>
  <div class="nav-bar">
    <!-- other elements -->
    <dropdown
      :options="someOptions"
      @option-select="onOptionSelect"
    ></dropdown>
  </div>
 </template>

  <script>
  export default {
    methods: {
      onOptionSelect (option) {
      // handle it here
      }
    }
  }
 </script>

3. Non Parent-Child communication

Sometimes two components may need to communicate with one-another but they are not parent/child to each other. In other words, global component communication.

You need to handle all the communications through event hub (or some call it event bus). In its simplest definition, it's an empty Vue instance that you use globally to fire and listen for events anywhere you want in the component's tree.

As I just mentioned, this eventHub is an empty Vue instance which you’d create like this:
 //empty Vue instance 
 var eventHub = new Vue()
This needs to be globally accessible
For everything to work correctly, you need to make sure that this eventHub is accessible globally. And this depends on how your application is structured.

If you’re building a simple non-modular app, you can attach that instance to the browser’s window object.

However, for module-based applications that use bundling tools like Webpack or Browserify, you can do that by exporting that instance from a certain file and then import it wherever you need it.
 // src/shared/EventHub.js

 import Vue from 'vue'
 export default new Vue()
Now in other components, you would import it like this:
 import eventHub from 'src/shared/EventHub'
Let’s imagine that we want to pass an event from component-A to component-B. To do that, we first need to emit that event from component-A:
 import eventHub from 'src/shared/EventHub'

// …

methods: {
  doSomething () {
    eventHub.$emit('id-selected', 1)
  }
}
Now, in component-B, you would listen to it like this:
 import eventHub from 'src/shared/EventHub'

 // …

 mounted () {
 // in component B's created hook  
 eventHub.$on('id-selected', (id) => {
     // handle it however you want.
   })
 }

As simple as that! The great thing about this approach is that you're no longer concerned about the components tree structure. In other words, events can be emitted from any component and handled by any other component regardless of their relationship.

In more complex cases, you should consider to use Vuex.

Sources:
Understanding Components Communication in Vue 2.0
Vue.js Components

No comments:

Post a Comment