Working With Functional Vue.js Components

On my journey to find ways to improve the rendering performance of large scale Vue.js applications, I stumble upon functional components as a possible solution from time to time. But so far I’ve always found one or two reasons why I can’t use them in my application.

Special characteristics of functional components

Functional components have some restrictions that cause them to behave differently than other components. For example, attributes are not passed along automatically, i.e. classes or ID’s specified on a functional component are ignored by default.

<!-- src/components/ArticleTeaser.vue -->
<template>
  <div class="ArticleTeaser">
    <UiHeadline
      id="hyphenCase(article.title)"
      class="ArticleTeaser__title"
      @click="readMore"
    >
      {{ article.title }}
    </UiHeadline>
    <p>{{ article.intro }}</p>
  </div>
</template>
<!-- src/components/UiHeadline.vue -->
<template functional>
  <h1>
    <slot/>
  </h1>
</template>

In the above example, none of the attributes would work on the template-based functional UiHeadline component. There would be no id or class and also the @click event handler would not be fired.

The biggest problem with that, in my opinion, is that this is not transparent in any way. Imagine that the person who created the UiHeadline component is not the same person who is responsible for the ArticleTeaser component - developers who only use the UiHeadline component might have no idea why these attributes don’t work, at least not until they take a closer look at the source code.

But fortunately, there are ways to solve this, and it is the responsibility of the developer who builds the functional component to make it behave like a normal component.

Passing along attributes and event listeners

Let’s start with making it possible to pass along attributes and event listeners just as with regular components.

<template functional>
  <h1
    v-bind="data.attrs"
    v-on="listeners"
  >
    <slot/>
  </h1>
</template>

Here you can see that with v-bind we can pass on all HTML attributes and we can use v-on to do the same with event listeners.

Although you may think that this should do the trick, we are not quite finished yet, because unfortunately the class attribute is not part of data.attrs.


Do you want to learn more about advanced Vue.js techniques?

Register for the Newsletter of my upcoming book: Advanced Vue.js Application Architecture.


Passing classes, styles and refs to functional components

Because Vue.js applies a special logic to the class, style and ref attributes, they are not part of data.attrs, but instead you can access classes, styles and refs via data.class / data.staticClass, data.style / data.staticStyle and data.ref.

<!-- Goes into `data.class` -->
<UiHeadline :class="['my-class']"/>

<!-- Goes into `data.staticClass` -->
<UiHeadline class="my-class"/>

This means that if you want to make your template-based functional components fully transparent, you have to apply data.class and data.staticClass as well as data.style and data.staticStyle and also data.ref manually.

<template functional>
  <h1
    :ref="data.ref"
    :class="[data.class, data.staticClass]"
    :style="[data.style, data.staticStyle]"
    v-bind="data.attrs"
    v-on="listeners"
  >
    <slot/>
  </h1>
</template>

Now our functional UiHeadline component is fully transparent and it doesn’t matter to the people who use it that they’re actually dealing with a functional component. Many thanks to Nico Prat, who made me aware that the style and ref attributes must also be taken into account.

Benchmarks

You may be wondering why you should go the extra mile using functional components when you have to do all this extra work to use them. First, there are situations where you may not need to pass on attributes and event listeners, and second, functional components are usually much faster than normal components. Take a look at the following benchmark performed by Austin Gil to see the potential performance benefits.

Wrapping it up

If you plan to use template-based functional components, I highly recommend that you pass on all attributes, classes, and event listeners to avoid confusion for those who need to use these components as building blocks in their own components.

Apart from these caveats, refactoring parts of your code base to use of functional components can be a great way to improve the performance of your Vue.js powered application.

References


Do you want to learn how to build advanced Vue.js applications?

Register for the Newsletter of my upcoming book: Advanced Vue.js Application Architecture.



Do you enjoy reading my blog?

You can buy me a ☕️ on Ko-fi!