As mentioned by Chris Fritz (Vue.js Core Team Emeriti) in VueCONF US 2019
if we had Kia enter
.native
and then the root element of the base input changed from an input to a label suddenly this component is broken and it's not obvious and in fact, you might not even catch it right away unless you have a really good test. Instead by avoiding the use of the.native
modifier which I currently consider an anti-pattern will be removed in Vue 3 you'll be able to explicitly define that the parent might care about which element listeners are added to...
$listeners
:So, if you are using Vue 2 a better option to resolve this issue would be to use a fully transparent wrapper logic. For this Vue provides a $listeners
property containing an object of listeners being used on the component. For example:
{
focus: function (event) { /* ... */ }
input: function (value) { /* ... */ },
}
and then we just need to add v-on="$listeners"
to the test
component like:
Test.vue (child component)
<template>
<div v-on="$listeners">
click here
</div>
</template>
Now the <test>
component is a fully transparent wrapper, meaning it can be used exactly like a normal <div>
element: all the listeners will work, without the .native
modifier.
Demo:
Vue.component('test', {_x000D_
template: `_x000D_
<div class="child" v-on="$listeners">_x000D_
Click here_x000D_
</div>`_x000D_
})_x000D_
_x000D_
new Vue({_x000D_
el: "#myApp",_x000D_
data: {},_x000D_
methods: {_x000D_
testFunction: function(event) {_x000D_
console.log('test clicked')_x000D_
}_x000D_
}_x000D_
})
_x000D_
div.child{border:5px dotted orange; padding:20px;}
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>_x000D_
<div id="myApp">_x000D_
<test @click="testFunction"></test>_x000D_
</div>
_x000D_
$emit
method:We can also use $emit
method for this purpose, which helps us to listen to child components events in parent component. For this, we first need to emit a custom event from child component like:
Test.vue (child component)
<test @click="$emit('my-event')"></test>
Important: Always use kebab-case for event names. For more information and demo regading this point please check out this answer: VueJS passing computed value from component to parent.
Now, we just need to listen to this emitted custom event in parent component like:
App.vue
<test @my-event="testFunction"></test>
So, basically instead of v-on:click
or the shorthand @click
we will simply use v-on:my-event
or just @my-event
.
Demo:
Vue.component('test', {_x000D_
template: `_x000D_
<div class="child" @click="$emit('my-event')">_x000D_
Click here_x000D_
</div>`_x000D_
})_x000D_
_x000D_
new Vue({_x000D_
el: "#myApp",_x000D_
data: {},_x000D_
methods: {_x000D_
testFunction: function(event) {_x000D_
console.log('test clicked')_x000D_
}_x000D_
}_x000D_
})
_x000D_
div.child{border:5px dotted orange; padding:20px;}
_x000D_
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>_x000D_
<div id="myApp">_x000D_
<test @my-event="testFunction"></test>_x000D_
</div>
_x000D_
v-bind="$attrs"
:Vue 3 is going to make our life much easier in many ways. One of the examples for it is that it will help us to create a simpler transparent wrapper with very less config by just using v-bind="$attrs"
. By using this on child components not only our listener will work directly from the parent but also any other attribute will also work just like it a normal <div>
only.
So, with respect to this question, we will not need to update anything in Vue 3 and your code will still work fine as <div>
is the root element here and it will automatically listen to all child events.
Demo #1:
const { createApp } = Vue;_x000D_
_x000D_
const Test = {_x000D_
template: `_x000D_
<div class="child">_x000D_
Click here_x000D_
</div>`_x000D_
};_x000D_
_x000D_
const App = {_x000D_
components: { Test },_x000D_
setup() {_x000D_
const testFunction = event => {_x000D_
console.log("test clicked");_x000D_
};_x000D_
return { testFunction };_x000D_
}_x000D_
};_x000D_
_x000D_
createApp(App).mount("#myApp");
_x000D_
div.child{border:5px dotted orange; padding:20px;}
_x000D_
<script src="//unpkg.com/vue@next"></script>_x000D_
<div id="myApp">_x000D_
<test v-on:click="testFunction"></test>_x000D_
</div>
_x000D_
But for complex components with nested elements where we need to apply attributes and events to main <input />
instead of the parent label we can simply use v-bind="$attrs"
Demo #2:
const { createApp } = Vue;_x000D_
_x000D_
const BaseInput = {_x000D_
props: ['label', 'value'],_x000D_
template: `_x000D_
<label>_x000D_
{{ label }}_x000D_
<input v-bind="$attrs">_x000D_
</label>`_x000D_
};_x000D_
_x000D_
const App = {_x000D_
components: { BaseInput },_x000D_
setup() {_x000D_
const search = event => {_x000D_
console.clear();_x000D_
console.log("Searching...", event.target.value);_x000D_
};_x000D_
return { search };_x000D_
}_x000D_
};_x000D_
_x000D_
createApp(App).mount("#myApp");
_x000D_
input{padding:8px;}
_x000D_
<script src="//unpkg.com/vue@next"></script>_x000D_
<div id="myApp">_x000D_
<base-input _x000D_
label="Search: "_x000D_
placeholder="Search"_x000D_
@keyup="search">_x000D_
</base-input><br/>_x000D_
</div>
_x000D_