In Vue, data is typically passed from parent components, to their children in a uni-directional fashion. This is transferred with props, which are the properties or attributes we give to components.
For example, if we call a component PageOne
, which has a prop called name
, that name
property would become available within the PageOne
component itself, letting us do what we want to do with it. In that way, the data is passed down to the child component, when we declare it in a parent component or page:
In most scenarios, props allow us to do everything we need to do with data. Sometimes, however, we need to emit data upwards - from a child component to its parent. For this, we use $emit
, which lets us send data upwards, and then trigger an event in the parent component should an $emit
event be fired.
How $emit works in Vue
There are three ways to fire $emit
in Vue, depending on if you're using the Options API, Composition API, or inlining your $emit
events. If you are unsure, you can read about the difference between the Composition API and Options API here.
-
this.$emit
within the Options API. -
$emit
if used in your HTML template. -
defineEmits
andemit
if used in the Composition API.
Let's take a look at how this works, through a silly example. Let's say we have a counter component, which looks like this:
<template>
<button @click="$emit('counterEvent')">Click Me</button>
</template>
This component is stored in a file called Counter.vue
. Our component can't be changed as it's used in other places, but it does have an $emit
event fired any time it is clicked. This is perfect, since we can use this in our parent component.
So what if we want to add this component somewhere - for example, in our App.vue
file - and use it to display the value of our counter. Let's try doing that now:
<template>
<h1>{{ counter }}</h1>
<Counter @counter-event="incrCounter"/>
</template>
<script>
import Counter from './Counter.vue'
export default {
// Add our components
components: {
Counter
},
// Store our data
data() {
return {
counter: 0
}
},
methods: {
incrCounter: function() {
this.counter += 1;
}
}
}
</script>
Let's break this down - first of all, we include our Counter
. Since it has an $emit
event called counterEvent
, we can attach that to our Counter
HTML. Whenever $emit
fires, it'll fire the counterEvent
, and thus the function within that property. Here, we run incrCounter
any time counterEvent
fires.
By doing that, we also increase our counter
data by 1, since that is what incrCounter
does. As such, we've emitted the click event upwards to our parent component.
Kebab Case
You may notice that when we defined our $emit
event, we used camel case (counterEvent
), but when tracking the event, we used kebab case (counter-event
).
In Vue 3 it is fine to use counterEvent
and counter-event
interchangably since Vue 3 automatically converts counterEvent
to counter-event
. In Vue 2, this functionality does not exist, so just stick with counter-event
for both.
Passing Data with $emit
Let's say instead, we want our component to define how much the counterEvent
should increase by. If we want to do that, we can pass a second argument to the $emit
function, which is the value:
<template>
<button @click="$emit('counterEvent', 2)">Click Me</button>
</template>
Here, we are passing the value 2
to our counterEvent
. Let's go back to our App.vue
file. To leverage this value in counterEvent
, we need to write it as a function. Below, n
is the value:
<template>
<h1>{{ counter }}</h1>
<Counter @counter-event="(n) => incrCounter(n)"/>
</template>
<script>
import Counter from './Counter.vue'
export default {
// Add our components
components: {
Counter
},
// Store our data
data() {
return {
counter: 0
}
},
methods: {
incrCounter: function(value) {
this.counter += value;
}
}
}
</script>
Now, our counter will increase by the value put in the child component, allowing us to pass data to our parent component as well. As you'd expect, this is not limited to just numbers, but can include any data structure - including objects and strings.
Using $emit with the Options API
We've shown a quite simple example, but we could also have written our Counter.vue
child component using a function instead. Here is an example with the Options API, using this.$emit
:
<template>
<button @click="emitFunction">Click Me</button>
</template>
<script>
export default {
emits: [ 'counterEvent' ],
methods: {
emitFunction: function() {
this.$emit('counterEvent', 2)
}
}
}
</script>
This may prove to be a slightly cleaner way to use $emit
, especially if you want to do other things along with using $emit
whenever a button is clicked.
Adding your emit events to your prototype
You may note that we also defined our emit event in emits
on the prototype. This is good practice for two reasons:
- It lets you self-document the code by showing which emit events are possible in this component.
-
It helps you keep track of deprecated emits, since Vue will throw an error if an emit event is used but not found in the
emits
array.
Using $emit with the Composition API
We can use $emit
with the Composition API - the only difference is we have to use defineEmits
instead.
<template>
<button @click="emitFunction">Click Me</button>
</template>
<script setup>
import { defineEmits } from 'vue'
const emit = defineEmits(['counterEvent']);
const emitFunction = function() {
emit('counterEvent', 2)
}
</script>
defineEmits
is used to define a full list of all permissible emit
events. Here, we only have one, counterEvent
. If you had more than one, you could define them as so:
const emit = defineEmits(['counterEvent', 'anotherEvent', 'finalEvent']);
If you use an emit event not listed in defineEmits
, Vue will throw a warning, similar to using emits
on the Options API. Otherwise, you can then use the emit()
function to emit as usual, without needing to use the Options API at all.
Final Thoughts and Best Practices
Emit is a powerful tool for sending data back up to the parent when we have to. It means datastreams can be two way in Vue. When defining emit
code, the two main best practices are:
- Always define your emit events in either
emits
ordefineEmits
, which will help you keep your code clean and well documented - Normal convention in Vue 3 is to use kebab case (
this-is-kebab-case
) for HTML, and camel case (thisIsCamelCase
) in script. As such, it is best to follow this convention here too.
I hope you've enjoyed this guide on how $emit
works. Stay tuned for more Vue Content.