Constantin Druccdruc

Hey, I'm Constantin Druc!

I'm a web developer sharing everything I know about building web applications.

Do not call functions in Vue templates

Instead of calling functions directly in your vue templates, use computed properties. Why? you might ask.

The answer is simple: functions called within vue templates will execute every time the component re-renders. If those functions are computationally expensive, they can decrease the performance of your application. And you don't want that, do you? 😁

Using computed properties, you can avoid this problem and ensure your functions execute only when necessary.

Consider this example:

<script setup>
import {ref} from "vue";
const count = ref(0);
const name = ref("Constantin");

function greeting() {
  console.log('greeting() was executed');
  return "Hello, " + name.value + "!";
}
</script>

<template>
  <label>
    Name<br/> 
    <input v-model="name"/> 
  </label>

  <h3>{{ greeting() }}</h3>

  <div>
    <button @click="count++">Increment</button>	
    <div>Count is: {{ count }}</div>
  </div>
</template>

In the example above, greeting() uses name and returns a "Hello, :name!" message.

Whenever name is changed using the input field, the template re-renders and greeting() re-executes to show an updated message.

However! greeting() should not execute when something unrelated to it changes. If you click the "Increment" button which updates count, the template re-renders causing greeting() to execute. This leads to unnecessary computations and decreases the performance of your app.

To avoid this issue, you should use computed properties. Computed properties are cached and re-evaluated only when the reactive values they depend on change.

Using computed properties

<script setup>
import {ref, computed} from "vue";
const count = ref(0);
const name = ref("Constantin");

const greeting = computed(() => {
  console.log('greeting() was executed');
  return "Hello, " + name.value + "!";
});
</script>

<template>
  <label>
    Name<br/> 
    <input v-model="name"/> 
  </label>

  <h3>{{ greeting }}</h3>

  <div>
    <button @click="count++">Increment</button>	
    <div>Count is: {{ count }}</div>
  </div>
</template>

In the example above, greeting only re-evaluates when name changes. You can click "Increment" a billion times, the greeting computed property won't care; it only cares about its dependencies (name).

You might say: Ok, but, uhm... what about sending parameters? You cannot send parameters to a computed property!

And you'd be correct. But before reaching out to a function, make sure there's definitely no way of using a computed property.

Here's a common trap I see people fall into:

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

const people = ref([
  {id: 1, firstName: 'Constantin', lastName: 'Druc'},
  {id: 2, firstName: 'Jack', lastName: 'Dorsey'},
  {id: 3, firstName: 'Bill', lastName: 'Burr'},
  {id: 4, firstName: 'Hugh', lastName: 'Jackman'},
  {id: 5, firstName: 'Tracey', lastName: 'Johnes'},
]);

function fullName(firstName, lastName) {
  console.log('fullName() was executed');
  return firstName + ' ' + lastName;
}
</script>

<template>
  <ul>
    <li v-for="person in people" :key="person.id">
      {{ fullName(person.firstName, person.lastName) }}
    </li>
  </ul>
</template>

In the example above, we have a people array, we loop through it in the template, and use a fullName() function to display the person's full name.

The code works, but it is not ideal. fullName() executes for every item in the array, and not only that, it will re-execute every time the template re-renders. Say you have an array of 50 people. The function will execute 50 times when the component first renders and then 50 more times every time any reactive value changes inside the template. Not good!

You can, however, use a computed property. Not for fullName, because you cannot pass parameters to computed properties, but for the whole people array.

Here's how:

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

const people = ref([
  {id: 1, firstName: 'Constantin', lastName: 'Druc'},
  {id: 2, firstName: 'Jack', lastName: 'Dorsey'},
  {id: 3, firstName: 'Bill', lastName: 'Burr'},
  {id: 4, firstName: 'Hugh', lastName: 'Jackman'},
  {id: 5, firstName: 'Tracey', lastName: 'Johnes'},
]);

const newPeople = computed(() => {
  console.log('newPeople was executed');

  return people.value.map(person => {
    return {
      ...person,
      fullName: person.firstName + ' ' + person.lastName
    }
  })
});
</script>

<template>
  <ul>
    <li v-for="person in newPeople" :key="person.id">
      {{ person.fullName }}
    </li>
  </ul>
</template>

I am not saying you can and should always use a computed property; I'm saying make sure you cannot use one; because if you can, you definitely should! The performance benefits will add up!

Tags: