Imagina que tu aplicación funciona a la perfección durante los primeros 10 minutos, pero después de navegar un poco, el navegador empieza a sentirse pesado, las animaciones se entrecortan y, finalmente, la pestaña colapsa. Este es el síntoma clásico de una fuga de memoria (memory leak). Es un problema silencioso que puede destruir la experiencia de usuario en aplicaciones de larga duración (SPAs).
Una Fuga de Memoria en Vue.js ocurre cuando un componente se desmonta (se elimina del DOM), pero sus referencias (como Event Listeners, temporizadores o suscripciones a APIs) permanecen activas en la memoria JavaScript. Esto impide que el "Garbage Collector" libere ese espacio, acumulando basura digital hasta que el navegador se queda sin recursos.
El Invitado Zombie: Entendiendo el Problema
Analogía: Piensa en un componente de Vue como una "Fiesta en una Casa". Cuando la fiesta termina (el componente se desmonta), esperas que todos se vayan y la casa quede vacía.
Una fuga de memoria es como un invitado (un Event Listener o un setInterval) que se escondió en el armario. Tú cierras la casa con llave, pero el invitado sigue adentro consumiendo comida y electricidad. Si haces 10 fiestas y en cada una se queda un invitado escondido, pronto tu casa estará llena de "zombies" y no podrás moverte.
En términos técnicos, Vue maneja perfectamente la limpieza de su propio DOM y directivas (como v-if o @click). Sin embargo, Vue no puede limpiar automáticamente lo que tú creas fuera de su alcance, como eventos globales en window, librerías de terceros instanciadas manualmente o temporizadores de JavaScript.
Implementación: El Patrón de Limpieza Correcto
Para solucionar esto, debemos usar los "Hooks del Ciclo de Vida". En Vue 3 (Composition API), nuestra arma principal es onBeforeUnmount. Este hook se ejecuta justo antes de que el componente sea destruido, dándonos la oportunidad de limpiar el desorden.
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
// Estado reactivo
const windowWidth = ref(window.innerWidth);
let intervalId = null;
// Función manejadora (debe ser nombrada para poder removerse)
const handleResize = () => {
windowWidth.value = window.innerWidth;
console.log('Redimensionando...');
};
onMounted(() => {
// 1. Evento Global: Escucha cambios en 'window'
window.addEventListener('resize', handleResize);
// 2. Temporizador: Se ejecuta cada segundo
intervalId = setInterval(() => {
console.log('Polling de datos...');
}, 1000);
});
// EL FIX: Limpieza explícita
onBeforeUnmount(() => {
// Eliminar el listener (CRÍTICO: debe ser la misma referencia a la función)
window.removeEventListener('resize', handleResize);
// Limpiar el intervalo
if (intervalId) {
clearInterval(intervalId);
}
console.log('Componente desmontado y memoria liberada.');
});
</script>
Cuidado con las Funciones Anónimas: Nunca hagas esto: window.addEventListener('scroll', () => { ... }). Si usas una función flecha anónima directamente, no podrás removerla con removeEventListener porque no tienes una referencia a ella. Siempre define la función en una variable o método antes de pasarla.
Cómo Detectar Fugas con Chrome DevTools
No tienes que adivinar; puedes ver la memoria en tiempo real. Sigue estos pasos para realizar una "Auditoría de Heap Snapshot":
- Abre tu aplicación y las Chrome DevTools (F12).
- Ve a la pestaña Memory y selecciona Heap Snapshot.
- Paso Base: Haz clic en el icono de "Papelera" (Collect Garbage) para forzar una limpieza inicial y toma el primer "Snapshot".
- Acción de Fuga: Navega hacia la ruta que sospechas que tiene el problema (monta el componente) y luego navega hacia otra página (desmóntalo). Repite esto 5-10 veces.
- Paso Final: Fuerza el Garbage Collector nuevamente (Papelera) y toma un segundo Snapshot.
- Análisis: Selecciona el segundo Snapshot y cambia la vista de "Summary" a "Comparison". Ordena por "Delta". Si ves números positivos crecientes bajo componentes o nodos DOM "Detached" (Desconectados), has encontrado tu fuga.
Preguntas Frecuentes (FAQ)
P. ¿Qué pasa si uso <KeepAlive> en mis componentes?
R. Excelente pregunta. Si usas <KeepAlive>, el componente no se desmonta realmente, solo se desactiva. Por lo tanto, onBeforeUnmount no se ejecutará al cambiar de ruta. En este caso, debes usar los hooks onActivated (para agregar eventos) y onDeactivated (para limpiarlos).
P. ¿Las directivas de Vue como @click causan fugas?
R. Generalmente no. Vue gestiona automáticamente los listeners que añades directamente en el template (ej: <button @click="fn">). Cuando Vue destruye el elemento DOM, también elimina esos listeners. El problema surge principalmente con referencias globales (window/document) o librerías externas que manipulan el DOM manualmente.
P. ¿Cómo limpio librerías de terceros como Chart.js o Leaflet?
R. Casi todas las librerías "pesadas" tienen un método de destrucción. Por ejemplo, en Chart.js debes llamar a chartInstance.destroy() dentro de onBeforeUnmount. Si olvidas esto, la instancia del gráfico retendrá referencias al elemento canvas eliminado, causando una fuga masiva de memoria.
Post a Comment