Capítulo 04: Propriedades Computadas (computed) e Observadores (watch, watchEffect)
Capítulo 04: Propriedades Computadas (computed) e Observadores (watch, watchEffect)
Especialização em Front-End com Vue • Vue 3 & Nuxt 3 • Composition API, Pinia, Vue Router, SSR e TypeScript
🗺️ Mapa Conceitual de Derivações e Efeitos Colaterais
flowchart TD
A["Mutações em Estados Reativos (Refs / Reactive)"] --> B{"Natureza da Operação Desejada"}
B -->|"Derivação Pura de Dado (Com Caching)"| C["computed()"]
B -->|"Efeito Colateral Explícito (Async/I/O)"| D["watch(source, callback)"]
B -->|"Efeito com Rastreamento Automático"| E["watchEffect(callback)"]
C --> F["Retorna Readonly Ref com Flag 'dirty'"]
D --> G["Recebe (novoValor, antigoValor)"]
E --> H["Executa imediatamente e rastreia deps"]
G & H --> I["onCleanup: Aborta timers / requisições pendentes"]
F --> J["Reutiliza Cache até dependência mudar"]
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style C fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style D fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style E fill:#fce4ec,stroke:#e91e63,stroke-width:2px
🏛️ 1. Derivações Puras vs Efeitos Colaterais: A Arquitetura Reativa
No desenvolvimento front-end com Vue 3, separar derivações puras de dados de efeitos colaterais (side effects) é essencial para evitar bugs de sincronização, vazamentos de memória e re-renderizações infinitas.
1.1 computed(): Memoização e Flag Suja (Dirty Checking)
Uma propriedade computada (computed) encapsula uma computação puramente declarativa:
- Memoização Inteligente: O Vue calcula o valor derivado e armazena o resultado em cache. Leituras subsequentes retornam o valor em cache instantaneamente em complexidade $O(1)$.
- Dirty Checking Reativo: Quando qualquer dependência reativa associada muda, o Vue não recalcula a função imediatamente; ele apenas marca a flag interna
dirty = true. O recálculo real só acontece quando a propriedade for efetivamente lida novamente por uma função de renderização ou outro consumidor ativo (Lazy Evaluation). - Writable Computed: Permite expor uma interface de escrita através de um objeto com métodos
get()eset(novoValor).
1.2 watch(): Observação Explícita e Controle Fino
O watch destina-se a disparar operações assíncronas (chamadas HTTP, manipulação de APIs do navegador como localStorage, disparos de métricas) em resposta à mudança de uma fonte específica:
- Fontes Válidas: Uma
Ref, uma função getter que retorna um valor (() => state.user.id), ou um array de fontes ([userId, role]). - Opções Avançadas:
immediate: true: Executa a função de callback imediatamente na montagem do componente.deep: true: Percorre recursivamente todas as propriedades aninhadas de um objeto.flush: 'post': Aguarda o Vue atualizar o DOM antes de executar o callback, permitindo inspecionar nós atualizados com segurança.
1.3 watchEffect(): Rastreamento Implícito e Cleanup
Diferente do watch, o watchEffect não exige declarar fontes explicitamente. Qualquer ref lida dentro do corpo do callback é automaticamente registrada como dependência. Executa imediatamente no início e aceita um parâmetro onCleanup(callback) para descartar tarefas pendentes (race conditions).
⚖️ 2. Matriz Decisória: computed vs watch vs watchEffect
| Critério Arquitetural | computed() |
watch() |
watchEffect() |
|---|---|---|---|
| Objetivo Primário | Derivar novos dados a partir do estado existente. | Disparar efeito colateral assíncrono em fonte alvo. | Disparar efeito colateral com rastreamento automático. |
| Possui Cache / Memoização? | Sim (fundamental). | Não. | Não. |
| Executa na Montagem? | Avaliado sob demanda (Lazy). | Apenas com { immediate: true }. |
Sempre executa imediatamente. |
| Acesso ao Valor Anterior? | Não. | Sim (newVal, oldVal). |
Não. |
| Fontes de Dependência | Automáticas dentro da função getter. | Explícitas (ref, getter ou array). | Implícitas (rastreadas na execução). |
| Efeitos Assíncronos / HTTP | ❌ PROIBIDO (deve ser função pura). | ✅ RECOMENDADO. | ✅ RECOMENDADO (com onCleanup). |
⚠️ 3. Armadilhas Comuns em Produção (Gotchas & Anti-Patterns)
Anti-Pattern 1: Efeitos Colaterais ou Mutações dentro de computed()
Erro Comum: Modificar outro estado reativo ou disparar requisições assíncronas dentro de um getter computado:
// ❌ ANTI-PATTERN CRÍTICO
const userFullName = computed(() => {
api.post('/log', { name }); // Efeito colateral imperdoável em computed!
isLoggedIn.value = true; // Muta outro estado durante cálculo derivado!
return `${user.firstName} ${user.lastName}`;
});
Abordagem Correta: Funções de computed devem ser puras, determinísticas e livres de efeitos colaterais. Para logs e chamadas assíncronas, use watch.
Anti-Pattern 2: Observar Propriedade Primitiva de Objeto sem Getter Function
Erro Comum: Escrever watch(userProfile.age, (newAge) => ...) onde age é um número primitivo dentro de um reactive.
Abordagem Correta: Passar uma função getter: watch(() => userProfile.age, (newAge) => ...).
💻 4. Código Conceitual Arquitetural Comentado
<!-- src/components/FinancialCalculator.vue -->
<script setup lang="ts">
import { ref, computed, watch, watchEffect } from 'vue';
// 1. Estados Reativos de Entrada
const rawAmount = ref<number>(1000);
const taxRate = ref<number>(0.15); // 15%
const queryParam = ref<string>('');
// 2. Computed Pura com Cache Memoizado
const calculatedTax = computed<number>(() => {
return rawAmount.value * taxRate.value;
});
// 3. Writable Computed com Getter e Setter Bidirecionais
const totalWithTax = computed<number>({
get: () => rawAmount.value + calculatedTax.value,
set: (newTotal: number) => {
// Reverte a base a partir do novo total desejado
rawAmount.value = Number((newTotal / (1 + taxRate.value)).toFixed(2));
}
});
// 4. Watch com Debounce e Limpeza de Race Conditions (onCleanup)
watch(queryParam, (term, _, onCleanup) => {
if (!term.trim()) return;
const timer = setTimeout(() => {
console.log(`[API Async Watch] Buscando relatórios fiscais para: ${term}`);
}, 400);
// Cancela o timer se o usuário digitar uma nova tecla antes de 400ms
onCleanup(() => clearTimeout(timer));
});
// 5. WatchEffect com Atualização Automática de Telemetria
watchEffect(() => {
console.log(`[Audit] Total Fiscal recalculado: R$ ${totalWithTax.value.toFixed(2)}`);
});
</script>
<template>
<div class="max-w-md mx-auto p-6 bg-white border rounded-2xl shadow-sm space-y-4">
<h3 class="font-bold text-slate-800 text-lg">Calculadora Fiscal Corporativa</h3>
<div class="space-y-2">
<label class="block text-xs font-semibold text-slate-600">Valor Base (R$):</label>
<input type="number" v-model.number="rawAmount" class="w-full border px-3 py-2 rounded-lg text-sm" />
</div>
<div class="p-4 bg-slate-50 border rounded-xl space-y-1 text-sm">
<div class="flex justify-between text-slate-600">
<span>Imposto (15%):</span>
<strong class="text-slate-800">R$ {{ calculatedTax.toFixed(2) }}</strong>
</div>
<div class="flex justify-between text-base font-bold text-indigo-600 border-t pt-2">
<span>Total com Imposto:</span>
<span>R$ {{ totalWithTax.toFixed(2) }}</span>
</div>
</div>
</div>
</template>
🛠️ 5. Cheat-Sheet de Sintaxes: computed e watch
// Computed somente leitura (padrão)
const dobro = computed(() => valor.value * 2);
// Computed bidirecional (Writable)
const valorCustom = computed({
get: () => valor.value,
set: (val) => { valor.value = val; }
});
// Watch em uma única Ref
watch(meuRef, (novo, antigo) => { ... });
// Watch em propriedade de reactive com Getter
watch(() => obj.prop, (novo, antigo) => { ... });
// Watch com opções Immediate e Deep
watch(filtro, callback, { immediate: true, deep: true });
// WatchEffect com Cleanup
watchEffect((onCleanup) => {
const controller = new AbortController();
fetch('/api/dados', { signal: controller.signal });
onCleanup(() => controller.abort());
});