No meu último post, falei sobre como o Vue executa funções de template e watch em lote. Porém, existe outra forma de reatividade no Vue: as propriedades computadas, usando o computed. O computed serve para estados derivados, aplicando operações sobre uma ou mais refs.

O computed também é um efeito, assim como watch e funções de template, mas possui sua própria regra de execução: ele só é reavaliado sob demanda.

<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>

Link para playground

Note no exemplo que a função de template lê o valor de upper apenas se showTitle for true. Experimente esconder o título e modificar o texto: o computed só será executado (e apenas uma vez) quando você mostrar o título novamente.

E como já sabemos que o Vue reexecuta a função de template em lote, mesmo que upper dependa de msg, ele só será recalculado quando a função de template for executada.

<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>

Link para playground

Note que nesse caso não é o upper que está sendo executado em lote, e sim a função de template. Se você acessar o valor de upper dentro da função newWords, a função do computed será reexecutada a cada acesso.

function newWords() {
  msg.value += " ";
  console.log(upper.value); // upper será executado
  msg.value += "Hello";
  console.log(upper.value); // upper será executado
  msg.value += " ";
  console.log(upper.value); // upper será executado
  msg.value += "World";
  console.log(upper.value); // upper será executado
  msg.value += "!";
  console.log(upper.value); // upper será executado
}

No entanto, o Vue também inclui outra otimização: cache. Se as dependências do computed não mudaram, a função não será reexecutada, independentemente da quantidade de acessos.

<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>

Link para o playground

Note que, mesmo acessando o valor de upper várias vezes, se o valor de msg não mudou, a função do computed não é executada novamente.

Conclusão

O Vue oferece otimizações automáticas para propriedades computed que garantem eficiência ao evitar execuções desnecessárias. Isso permite que você foque na lógica da sua aplicação sem se preocupar com performance.

Para aproveitar ao máximo esses benefícios:

  • Mantenha seus computed sempre puros (sem efeitos colaterais)
  • Evite operações custosas ou assíncronas dentro de computed
  • Use computed para dados derivados, em vez de múltiplas refs e watch