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. That way, the data is passed down to the child component when we declare it in the 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.
$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.
this.$emit
within the Options API.$emit
if used in your HTML template.defineEmits
and emit
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 there is 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
anytime counterEvent
fires.
By doing that, we can 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.
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.
$emit
Let's say instead, we want our components to define how much the counterEvent
should increase. 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.
$emit
with the Options APIWe'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.
You may note that we also defined our emit event in emits
on the prototype. This is good practice for two reasons:
emits
array.
$emit
with the Composition APIWe 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.
Emit is a powerful tool for sending data back up to the parent when we have to. This means datastreams can be two-way in Vue. When defining emit
code, the two main best practices are:
emits
or defineEmits
, which will help you keep your code clean and well documented.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.
Also Published Here