In my last post, I talked about how Vue batches template functions and watch effects. However, there's another form of reactivity in Vue: computed properties, using computed. The computed function is used for derived state, applying operations over one or more refs.
computed is also an effect, just like watch and template functions, but it has its own execution rule: it is only re-evaluated on demand.
<script setup>
import { ref, computed } from "vue";
const msg = ref("Hello World!");
const showTitle = ref(false);
const upper = computed(() => {
console.log("computed");
return msg.value.toUpperCase();
});
</script>
<template>
<div>
<button @click="showTitle = !showTitle">Toggle Title</button>
</div>
<input v-model="msg" />
<h1 v-if="showTitle">{{ upper }}</h1>
</template>
Notice in the example that the template function only reads the value of upper if showTitle is true.
Try hiding the title and modifying the text: the computed will only execute (and only once)
when you show the title again.
And as we already know that Vue batches template function executions, even though upper depends on msg,
it will only be recalculated when the template function is executed.
<script setup>
import { ref, computed } from "vue";
const msg = ref("Hello World!");
function newWords() {
msg.value += " ";
msg.value += "Hello";
msg.value += " ";
msg.value += "World";
msg.value += "!";
}
const upper = computed(() => {
console.log("computed");
return msg.value.toUpperCase();
});
</script>
<template>
<input v-model="msg" />
<button @click="newWords">More words</button>
<h1>{{ upper }}</h1>
</template>
Notice that in this case, it is not upper that is being batched, but the template function.
If you access the value of upper inside the newWords function, the computed function will be re-executed on every access.
function newWords() {
msg.value += " ";
console.log(upper.value); // upper will be executed
msg.value += "Hello";
console.log(upper.value); // upper will be executed
msg.value += " ";
console.log(upper.value); // upper will be executed
msg.value += "World";
console.log(upper.value); // upper will be executed
msg.value += "!";
console.log(upper.value); // upper will be executed
}
However, Vue also includes another optimization: caching. If the dependencies of the computed haven't changed,
the function won't be re-executed, regardless of how many times it is accessed.
<script setup>
import { ref, computed } from "vue";
const msg = ref("Hello World!");
const savedUpperMsg = ref([]);
function saveMsg() {
savedUpperMsg.value.push(upper.value);
console.log("adding to the list:", upper.value);
}
const upper = computed(() => {
console.log("computed");
return msg.value.toUpperCase();
});
</script>
<template>
<input v-model="msg" />
<button @click="saveMsg">Save Msg</button>
<div v-for="msg in savedUpperMsg">
{{ msg }}
</div>
</template>
Notice that, even when accessing the value of upper multiple times, if the value of msg hasn't changed,
the computed function is not executed again.
Conclusion
Vue provides automatic optimizations for computed properties that ensure efficiency by avoiding
unnecessary executions. This allows you to focus on your application's logic without worrying
about performance.
To make the most of these benefits:
- Keep your
computedproperties pure (no side effects) - Avoid expensive or asynchronous operations inside
computed - Use
computedfor derived data, instead of multiplerefsandwatch