8 secrets Vue Developers must know
In an inline statement handler besides the $event
special variable we have access to another special variable: arguments
. $event
will have access only to the first argument, whilst the arguments
will have access to all arguments.
Detailed Explanation:
In an inline statement handler we already know that we have access to the special $event
variable.
So if a child components emits an event with a parameter, we have access to it by using $event
:
Parent component
<template> // ... <Child @event="someVariable = $event" /> // ...
</template>
Child component
export default { //... emitEvent () { this.$emit('event', 'a simple text') } //...
}
That works really well when the child component, is a component that we have access to, since we can be sure we pass only one parameter.
But how if we use a third-party component/library (e.g. dropzone.js) which passes many arguments up via event ??
The $event
will have access only to the first argument. See this codesandbox example which illustrates that $event
will catch only the first argument. (Click the button and see the console)
In that case, instead of $event
we can use arguments
and all arguments will be accessible.
So in the codesandbox above, in order to make it work, we have to change the line 4 in the Child.vue
from:
<GrandChild @event="$emit('event', $event)"/>
to:
<GrandChild @event="$emit('event', arguments)"/>
You can emit event from functional components
Short Explanation:
Using functional components, means that we don't have access to this
context. Thus, we cannot do this.$emit()
. But... we have access to listeners
so we can do <button @click="listeners.clicked"></button>
Detailed Explanation:
Functional components are stateless (no reactive data) and instanceless (no this context). But functional components have access to some properties such as props
, children
etc and most importantly (for this case), listeners
.
According to Vue docs:
listeners: An object containing parent-registered event listeners. This is an alias to data.on
That means that we can emit event from functional components. Wiii
Simple example:
<template functional> <button @click="listeners['custom-event']('message from child')"> Button from child </button>
</template>
Working example (click the button and open the console)
How if we want to emit an event from functional component with render functions? Can we do that? Of course!
Simple example:
export default { functional: true, render(createElement, { listeners }) { return createElement( "button", { on: { click: event => { const emit_event = listeners.event_from_child; emit_event("Hello World!Is this the message we excpected? :/"); } } }, "Pass event to parent" ); }
};
Working example
Someone may wonder if we can use .sync
Modifier using this approach.
The answer ? Of course!
<button @click="listeners['update:message']('some text')">Click me</button>
Pass all props to the child component
Say that we have a component which receives props
and we want to pass all those props to a child component. To achieve that, we can do:
<ChildComponent v-bind="$props" />
Taking advantage of v-bind
we can also have an object like:
data: () =>({ obj: { firstName: 'John', lastName: 'Doe', age: 30 }
})
And pass firstName
, lastName
, age
as props
to a child component, like:
Watch child properties changes from parent
You may wonder why to do that and you are right! That's a bad practice. But sometimes you use a third-party component and you want to watch their properties to fit your needs.
A long time ago, in a project we were using a date picker and we wanted to be able to detect when the popup was visible. Unfortunately there was no option to do that.I discovered that the date picker was using a popupVisible
reactive property but it wasn't exposed by the library. So I had to somehow watch this property from my component.
The below code is NOT able to detect changes:
watch: { '$refs.datePicker.popupVisible': { handler (new_value) { console.log('value changed') }, deep: true } }
In order to detect changes of a child component's property you should do:
mounted() { this.$watch( "$refs.picker.popupVisible", (new_value, old_value) => { //execute your code here } ); }
Working example:
Learn more about vm.$watch
Listening for child event in the router-view
Most of you, should already know that since it is straightforward, but I've asked many times about the below question.
Say you have a component which has nested routes:
<template> //... <router-view></router-view> //...
</template>
And you have a nested route as follow:
<template> //... <button @click="$emit('event')"></button> //...
</template>
So the component corresponding to a nested route, emits an event. The question is : how to listen to that event ?
The simple answer demonstrated with code:
<template> //... <router-view @event="callAMethod"></router-view> //...
</template>
Exactly! We listen to that event in the router-view
component
Vue components lifecycle hooks don't run in the order you think they run
Say you have 2 pages. Home and About.
When switching from Home to About, the created
hook of the About component will run before the beforeDestroy
hook of Home component. (take a moment here)
Weird?? Try switching routes in the working example below by seeing the console.
As solution (idk if it's the best solution though) you can use transition-mode out-in
<transition mode="out-in"> <router-view></router-view> </transition>
How to know if a child component is mounted ?
This one of my favorite tips I've read here (Vue Dose)
Say you have a child component and you want to do something when a hook of the child component is executed. You can do:
<Child @hook:created="doSomething" />
So the question to How to know when a child component mounted is:
<Child @hook:mounted="componentMountedDoSomething" />
How to know if a dynamically vuex registered module is registered ?
With the power of dynamically register/unregister vuex modules we can improve the performance a lot.
I recommend you to read a very useful article: Performance optimization: Lazy load vuex modules
We can register a vuex module:
this.$store.registerModule('my-module', MyModule)
And unregister it:
this.$store.unregisterModule('my-module')
To know if a module is already registered:
if (Object.keys(this.$store._modules.root._children).includes('my-module')) { // module is registered
}
My time is very limited but I am glad that I found some time to write this article (it took me some time though). I hope it helped and you enjoyed reading it.