Capítulo 06: Ciclo de Vida na Composition API (onMounted, onUnmounted)
Capítulo 06: Ciclo de Vida na Composition API (onMounted, onUnmounted)
Especialização em Front-End com Vue • Vue 3 & Nuxt 3 • Composition API, Pinia, Vue Router, SSR e TypeScript
🗺️ Mapa Conceitual do Ciclo de Vida de um Componente Vue 3
flowchart TD
A["Inicialização do Componente"] --> B["setup() / <script setup> (Executa no Server e Client)"]
B --> C["Criação e Compilação do Virtual DOM"]
C --> D["onMounted (Client-Only: DOM Real Disponível)"]
D --> E{"Mutações Reativas (State Change)"}
E -->|Sim| F["onBeforeUpdate -> Reconciliação do VDOM -> onUpdated"]
F --> E
E -->|Destruição / Navegação| G["onBeforeUnmount"]
G --> H["onUnmounted (Client-Only: Limpeza de Timers e Listeners)"]
subgraph ERROR_HANDLING["Gestão de Exceções"]
I["onErrorCaptured: Intercepta erros de filhos"]
end
B & D & F -.-> ERROR_HANDLING
style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style B fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style D fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
style H fill:#fce4ec,stroke:#e91e63,stroke-width:2px
style ERROR_HANDLING fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
🏛️ 1. O Ciclo de Vida na Era da Composition API
Na Composition API, os hooks de ciclo de vida são importados diretamente como funções independentes (import { onMounted, onUnmounted } from 'vue'). Ao contrário da Options API, onde existiam opções únicas (mounted() { ... }), na Composition API você pode invocar o mesmo hook múltiplas vezes dentro de funções ou composables separados, promovendo alto grau de modularidade.
1.1 setup() vs onMounted: A Fronteira entre Servidor e Navegador
- Corpo de
<script setup>: Equivale aos antigosbeforeCreateecreated. Executa tanto no servidor (durante o Server-Side Rendering do Nuxt 3) quanto no navegador do cliente. Nunca acessewindow,documentou APIs de terceiros que dependem de DOM diretamente no nível raiz de setup. onMounted: Disparado exclusivamente no cliente logo após todos os elementos do componente e seus nós do DOM filhos terem sido inseridos no documento HTML. É o local seguro para instanciar gráficos (Chart.js), mapas (Leaflet), focar inputs ou registrar eventos globais de tela.
1.2 onUnmounted e Prevenção de Memory Leaks
Em Single Page Applications (SPAs), quando o usuário navega entre páginas, os componentes antigos são destruídos, mas o JavaScript da aplicação continua em execução na memória. Qualquer timer (setInterval), WebSocket, observador de redimensionamento (ResizeObserver) ou listener (window.addEventListener) não removido em onUnmounted causará Memory Leak e degradação contínua de performance.
1.3 nextTick() e o Ciclo Assíncrono do DOM
O Vue não atualiza o DOM instantaneamente no momento em que um ref é modificado. Ele enfileira as mutações em um buffer e as aplica em lote na próxima microtarefa do navegador (batched updates).
- Se você modificar um estado e precisar ler imediatamente as novas dimensões de um nó do DOM (ex:
element.scrollHeight), você deve aguardar comawait nextTick().
⚖️ 2. Tabela de Correspondência: Options API vs Composition API
| Options API (Vue 2 / Legado) | Composition API (Vue 3) | Ambiente de Execução | Finalidade Principal |
|---|---|---|---|
beforeCreate / created |
Corpo de <script setup> |
Servidor + Cliente | Inicialização de estado reativo e injeção de dependências. |
beforeMount |
onBeforeMount |
Apenas Cliente | Imediatamente antes da inserção física no DOM. |
mounted |
onMounted |
Apenas Cliente | Acesso garantido ao DOM, inicialização de plugins externos. |
beforeUpdate / updated |
onBeforeUpdate / onUpdated |
Apenas Cliente | Notificação pós-reconciliação de nós do DOM. |
beforeUnmount / unmounted |
onBeforeUnmount / onUnmounted |
Apenas Cliente | Limpeza de recursos, timers, sockets e listeners globais. |
errorCaptured |
onErrorCaptured |
Servidor + Cliente | Barreira de contenção de erros de componentes descendentes. |
⚠️ 3. Armadilhas Comuns em Produção (Gotchas & Anti-Patterns)
Anti-Pattern 1: Acessar Objeto window ou document na Raiz do <script setup>
Erro Comum: Escrever const width = window.innerWidth; solto no script. Durante a renderização no servidor (SSR / Nuxt), o Node.js/Nitro lançará ReferenceError: window is not defined, derrubando o servidor.
Abordagem Correta: Mova o acesso para dentro de onMounted(() => { ... }) ou proteja com if (typeof window !== 'undefined').
Anti-Pattern 2: Esquecer de Remover Handlers de Eventos Globais
Erro Comum: window.addEventListener('keydown', handleKey) dentro de onMounted sem chamar window.removeEventListener('keydown', handleKey) em onUnmounted. Cada montagem do componente acumulará um listener zumbi duplicado na memória.
💻 4. Código Conceitual Arquitetural Comentado
<!-- src/components/SafeDomObserver.vue -->
<script setup lang="ts">
import { ref, onMounted, onUnmounted, nextTick } from 'vue';
const messageList = ref<string[]>(['Sistema inicializado.']);
const chatBoxRef = ref<HTMLDivElement | null>(null);
let heartbeatTimer: ReturnType<typeof setInterval> | null = null;
// Executado com segurança apenas no navegador
onMounted(() => {
console.log('[SafeDomObserver] Montado no cliente. Registrando listeners.');
window.addEventListener('resize', handleResize);
heartbeatTimer = setInterval(() => {
addMessage(`Heartbeat de sincronia: ${new Date().toLocaleTimeString()}`);
}, 3000);
});
// Limpeza cirúrgica contra Memory Leaks
onUnmounted(() => {
console.log('[SafeDomObserver] Desmontado. Limpando timers e listeners globais.');
window.removeEventListener('resize', handleResize);
if (heartbeatTimer) clearInterval(heartbeatTimer);
});
function handleResize(): void {
console.log(`[Viewport] Dimensão: ${window.innerWidth}x${window.innerHeight}`);
}
async function addMessage(text: string): Promise<void> {
messageList.value.push(text);
// Aguarda o DOM renderizar o novo elemento antes de medir scrollHeight
await nextTick();
if (chatBoxRef.value) {
chatBoxRef.value.scrollTop = chatBoxRef.value.scrollHeight;
}
}
</script>
<template>
<div class="max-w-md mx-auto p-5 bg-white border rounded-2xl shadow-sm space-y-3">
<h3 class="font-bold text-slate-800 text-base">Terminal de Eventos do Sistema</h3>
<!-- Template Ref associada ao container HTML -->
<div
ref="chatBoxRef"
class="h-40 bg-slate-900 text-emerald-400 p-3 rounded-xl font-mono text-xs overflow-y-auto space-y-1"
>
<div v-for="(msg, idx) in messageList" :key="idx">{{ msg }}</div>
</div>
</div>
</template>
🛠️ 5. Cheat-Sheet dos Hooks de Ciclo de Vida
import { onMounted, onUpdated, onUnmounted, onErrorCaptured, nextTick } from 'vue';
// Montagem no cliente (Acesso seguro ao DOM)
onMounted(() => {
// Inicialização de bibliotecas externas (Leaflet, D3, etc.)
});
// Desmontagem e desalocação de recursos
onUnmounted(() => {
// clearInterval(), abortController.abort(), removeEventListener()
});
// Aguardar sincronização da microtarefa do DOM
await nextTick();
// Captura e tratamento de erros de filhos (Error Boundary)
onErrorCaptured((err, instance, info) => {
console.error('Falha capturada:', err);
return false; // Interrompe propagação
});