DEV Community

Roland Doda
Roland Doda

Posted on • Updated on

7 Vue3 tips you need to know

VNode hooks

On each component or HTML tags, we can use some special (undocumented) hooks as event listeners. The hooks are:

  • onVnodeBeforeMount
  • onVnodeMounted
  • onVnodeBeforeUpdate
  • onVnodeUpdated
  • onVnodeBeforeUnmount
  • onVnodeUnmounted

I mainly use onVnodeMounted on components when I need to execute some code when the component is mounted or onVnodeUpdated to debug when something is updated but I am quite sure all of them can come in handy in some cases.

Example:

<script setup>
    import { ref } from 'vue'

    const count = ref(0)

    function onMyComponentMounted() {}

    function divThatDisplaysCountWasUpdated() {}
</script>

<template>
    <MyComponent @vnodeMounted="onMyComponentMounted" />
    <div @vnodeUpdated="divThatDisplaysCountWasUpdated">{{ count }}</div>
</template>
Enter fullscreen mode Exit fullscreen mode

It should be noted, that these hooks pass some arguments to the callback function. They pass only one argument which is the current VNode except for onVnodeBeforeUpdate and onVnodeUpdated that pass two arguments, the current VNode and the previous VNode.

Debugging hooks

We all know the lifecycle hooks that Vue provides us. But did you know that Vue 3 gives us two hooks that we can use for debugging purposes? They are:

onRenderTracked gets called for every reactive dependency that has been tracked.

<script setup>
import { ref, onRenderTracked } from 'vue'

const count = ref(0)
const count2 = ref(0)

// It will be called twice, once for count and once for count2
onRenderTracked((event) => {
    console.log(event)
})
</script>
Enter fullscreen mode Exit fullscreen mode

onRenderTriggered gets called when we trigger a reactivity update, or as the docs say it: "when a reactive dependency triggers the component's render effect to be re-run".

<script setup>
import { ref, onRenderTriggered } from 'vue'

const count = ref(0)

// It will be called when we update count
onRenderTriggered((event) => {
    debugger
})
</script>
Enter fullscreen mode Exit fullscreen mode

Expose slots from a child component

If you use a third-party component, chances are that you wrap the implementation of it in your own "wrapper" component. This is a good practice and scalable solution, but in that way, the slots of that third-party component are getting lost and we should find a way to expose them to the parent component:

WrapperComponent.vue

<template>
  <div class="wrapper-of-third-party-component">
    <ThirdPartyComponent v-bind="$attrs">

        <!-- Expose the slots of the third-party component -->
        <template v-for="(_, name) in $slots" #[name]="slotData">
            <slot :name="name" v-bind="slotData || {}"></slot>
        </template>

    </ThirdPartyComponent>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Now every component that uses WrapperComponent can use the slots of ThirdPartyComponent 🎉.

Scoped styles and multi-root nodes don't work well together

In Vue 3 we can finally have more than "one root node" components. That is great, but personally I fall into a design limitation when doing that. Imagine we have a child component:

<template>
  <p class="my-p">First p</p>
  <p class="my-p">Second p</p>
</template>
Enter fullscreen mode Exit fullscreen mode

And a parent component:

<template>
    <h1>My awesome component</h1>
    <MyChildComponent />
</template>

<style scoped>
// There is no way to style the p tags of MyChildComponent
.my-p { color: red; }
:deep(.my-p) { color: red; }
</style>
Enter fullscreen mode Exit fullscreen mode

There is no way from the scoped styling of the multi root parent component to style the child component's p tags.

So in short, a multi-root component, can't target multi-root child component's styles with scoped styles.

The best way to fix that, would be to wrap the parent or child component (or both) so we have only one root element.

But if you absolutely need both to have multi-root nodes, you can:

  • Use a non-scoped style
<style>
.my-p { color: red; }
</style>
Enter fullscreen mode Exit fullscreen mode
  • Use CSS Modules
<template>
    <h1>My awesome component</h1>
    <MyChildComponent :class="$style.trick" />
</template>

<style module>
.trick {
    color: red;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Since we are specifying a class here, then the multi-root child component has to explicitly specify the attribute fallthrough behavior.

If you want my opinion, unless you absolutely need a multi-root node component, go with a single root node and don't deal with this design limitation at all.

Be careful when using CSS selectors

#main-nav > li {} will be many times slower compared to .my-li { color: red }. From the docs:

Due to the way browsers render various CSS selectors, p { color: red } will be many times slower when scoped (i.e. when combined with an attribute selector). If you use classes or ids instead, such as in .example { color: red }, then you virtually eliminate that performance hit.

I highly recommend you to read Efficiently Rendering CSS if you want to dive deeper into this topic.

Boolean casting

In Vue 2 or early versions of Vue 3, for props with Boolean types we had different behavior depending on the order:

1st case:

props: {
  hoverColor: [String, Boolean] // <- defaults to ''
}
Enter fullscreen mode Exit fullscreen mode

2nd case:

props: {
  hoverColor: [Boolean, String] // <- defaults to false
}
Enter fullscreen mode Exit fullscreen mode

Not only that, but if you pass the prop like this:

<my-component hover-color></my-component>
Enter fullscreen mode Exit fullscreen mode

In the first case, it would be an empty string ''. In the second case, it would be true.

As you can see, this was a bit confusing and inconsistent. Fortunately, in Vue 3, we have a new behavior that is consistent and predictable:

Boolean behavior will apply regardless of type appearance order.

So:

hoverColor: [String, Boolean] // <- defaults to false
hoverColor: [Boolean, String] // <- defaults to false
hoverColor: [Boolean, Number] // <- defaults to false
Enter fullscreen mode Exit fullscreen mode

Template refs with v-for - Order is not guaranteed

remember this one so you don't lose hours of debugging trying to figure out what is going on

In the code below:

<script setup>
import { ref } from "vue";

const list = ref([1, 2, 3]);
const itemRefs = ref([]);
</script>

<template>
  <ul>
    <li v-for="item in list" ref="itemRefs" :key="item">
      {{ item }}
    </li>
  </ul>
</template>
Enter fullscreen mode Exit fullscreen mode

we are looping over the list array, and we create the itemRefs array.

itemRefs is not guaranteed to have the same order as list array.

If you want to find out more about this, you can read this issue.

Ending the article

I do have more tips and tricks, but this article became long already. Expect another article in the near future with even better tips/tricks.

Thank you all for reading, any feedback is highly appreciated!

Know a cool company hiring Vue.js developers?

Also, I am looking for a new job, so if you know a cool company that is hiring a Senior Vue.js developer, please reach out to rolanddoda2014@gmail.com 🙏.



Buy Me A Coffee

Latest comments (3)

Collapse
 
qq449245884 profile image
qq449245884

Dear Roland Doda,may I translate your article into Chinese?I would like to share it with more developers in China. I will give the original author and original source.

Collapse
 
jackzhoumine profile image
jackzhoumine

What is the link of the Chinese version?

Collapse
 
the_one profile image
Roland Doda

Hi, yes sure thank you for doing it!