Skip to content

VueJS - Computed Properties

Templates are great for simple expressions, but putting too much complex logic directly in your templates can make them cluttered and hard to maintain. For any complex logic involving reactive data, you should use computed properties.

Computed properties allow you to define a property that derives its value from other reactive state. They are cached based on their reactive dependencies, meaning they only re-evaluate when one of those dependencies changes.

Imagine you have an object with nested reactive data, and you want to display a message based on whether it has items.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Computed Property Example</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<h2>Author: {{ author.name }}</h2>
<!-- Directly in template (can get complex) -->
<p>Has published books? {{ author.books.length > 0 ? 'Yes' : 'No' }}</p>
<!-- Using computed property (cleaner) -->
<p>Has published books (Computed)? {{ publishedBooksMessage }}</p>
<h3>Books:</h3>
<ul>
<li v-for="(book, index) in author.books" :key="index">{{ book }}</li>
</ul>
<button @click="addBook">Add Book</button>
<button @click="removeBook">Remove Book</button>
</div>
<script>
const { createApp, reactive, computed } = Vue;
createApp({
setup() {
const author = reactive({
name: 'Jane Doe',
books: ['Vue 2 - Advanced Guide', 'Vue 3 - Basic Guide']
});
// Define a computed property
const publishedBooksMessage = computed(() => {
console.log('Computed property executing...'); // Log to see when it runs
// It depends on author.books
return author.books.length > 0 ? 'Yes' : 'No';
});
function addBook() {
author.books.push(`New Book #${author.books.length + 1}`);
}
function removeBook() {
if (author.books.length > 0) {
author.books.pop();
}
}
return {
author,
publishedBooksMessage, // Expose computed property to template
addBook,
removeBook
};
}
}).mount('#app');
</script>
</body>
</html>

In this example:

  • We have a reactive author object.
  • The first paragraph calculates the message directly in the template. This works, but if the logic were more complex, it would become unreadable.
  • The publishedBooksMessage is a computed property defined using Vue.computed(). It takes a getter function.
  • The computed property depends on author.books.length.
  • In the template, we access publishedBooksMessage like a regular property (no () needed).
  • Open your browser console. Notice that “Computed property executing…” logs only when you add or remove a book (i.e., when author.books changes), not every time the component re-renders. This demonstrates the caching behavior.

You might wonder why we need computed properties when we could achieve the same result with a method.

// Using a method instead
// setup() {
// ...
// function calculateBooksMessage() {
// console.log('Method executing...');
// return author.books.length > 0 ? 'Yes' : 'No';
// }
// return { author, calculateBooksMessage };
// }
// Template: <p>Has published books (Method)? {{ calculateBooksMessage() }}</p>

While methods can produce the same result, the key difference lies in caching. Computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when one of its dependencies has changed. In contrast, a method call will always run the function whenever a re-render happens.

When to use which?

  • Use computed properties when you need to derive state based on other reactive state, especially if the calculation is expensive or you want to benefit from caching.
  • Use methods when you don’t need caching, when you need to perform actions in response to events (like in @click), or when you need to pass arguments to the function.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Computed vs Method Caching</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<p>Computed Value: {{ computedValue }}</p>
<p>Computed Value: {{ computedValue }}</p> <!-- Accessing multiple times -->
<p>Method Value: {{ methodValue() }}</p>
<p>Method Value: {{ methodValue() }}</p> <!-- Calling multiple times -->
<button @click="triggerReRender">Trigger Re-render (Change unrelated state)</button>
<p>Unrelated Value: {{ unrelatedValue }}</p>
</div>
<script>
const { createApp, ref, computed } = Vue;
createApp({
setup() {
const unrelatedValue = ref(0);
const computedValue = computed(() => {
console.log('COMPUTED function running');
return Date.now(); // Example of a value that changes if re-run
});
function methodValue() {
console.log('METHOD function running');
return Date.now();
}
function triggerReRender() {
unrelatedValue.value++;
}
return {
computedValue,
methodValue,
triggerReRender,
unrelatedValue
};
}
}).mount('#app');
</script>
</body>
</html>

Run this example and open the console:

  1. On initial load, both ‘COMPUTED function running’ and ‘METHOD function running’ will log (likely twice for the method due to multiple template calls).
  2. Notice the timestamp displayed for the computed property is the same in both places, while the method’s timestamp might differ slightly.
  3. Click the ‘Trigger Re-render’ button. This changes unrelatedValue, causing a re-render.
  4. Observe the console: ‘METHOD function running’ logs again (twice), but ‘COMPUTED function running’ does not log. The computed property returns its cached value because its dependencies haven’t changed.
  5. The displayed timestamp for the computed property remains the same, while the method’s displayed timestamp updates.

Writable Computed Properties (Getters and Setters)

Section titled “Writable Computed Properties (Getters and Setters)”

By default, computed properties are getter-only. If you try to assign a new value to a computed property, you’ll get a runtime warning. However, you can create a writable computed property by providing both a get and a set function.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Writable Computed</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<div>First Name: <input v-model="firstName"></div>
<div>Last Name: <input v-model="lastName"></div>
<hr>
<div>Full Name (Computed): <input v-model="fullName"></div>
</div>
<script>
const { createApp, ref, computed } = Vue;
createApp({
setup() {
const firstName = ref('John');
const lastName = ref('Doe');
// Writable computed property
const fullName = computed({
// Getter: calculates value from source state
get() {
return firstName.value + ' ' + lastName.value;
},
// Setter: called when fullName is assigned a new value
set(newValue) {
console.log('Setter called with:', newValue);
// Update the source state based on the new value
const names = newValue.split(' ');
firstName.value = names[0] || ''; // Handle potential empty input
lastName.value = names.slice(1).join(' ') || ''; // Handle names with multiple parts
}
});
return { firstName, lastName, fullName };
}
}).mount('#app');
</script>
</body>
</html>

In this example:

  • The fullName computed property has both get and set.
  • The get function combines firstName and lastName.
  • The set function is triggered when you type into the ‘Full Name’ input field (because v-model tries to set fullName).
  • The setter splits the input value and updates the original firstName and lastName refs.
  • This creates a two-way binding effect: changing the first/last name updates the full name, and changing the full name updates the first/last name fields.

Computed properties are a powerful feature for creating clean, maintainable templates by encapsulating derived state logic within your component’s script.

Further Reading: Vue’s official documentation on Computed Properties: https://vuejs.org/guide/essentials/computed.html