Porque Vue solo limpia automáticamente los watchers creados de forma síncrona durante setup(). Cuando creas un watch o watchEffect dentro de un setTimeout, Promise.then o después de un await, Vue no puede vincularlo al ciclo de vida del componente. Sigue ejecutándose después de que el componente se desmonte.
onMounted(async () => {
await loadInitialData()
// Este watcher NO está vinculado al componente
watch(data, (newVal) => {
processData(newVal) // sigue ejecutándose tras el desmontaje
})
})El mismo problema con setTimeout:
onMounted(() => {
setTimeout(() => {
watchEffect(() => {
console.log(data.value) // sigue ejecutándose tras el desmontaje
})
}, 1000)
})Cómo solucionarlo
Opción 1 (preferida): Crea el watcher de forma síncrona con lógica condicional dentro.
const config = ref(null)
const userData = ref(null)
// Creado de forma síncrona, limpiado automáticamente al desmontar
watch(userData, (newData) => {
if (config.value && newData) {
applySettings(config.value, newData)
}
})
onMounted(async () => {
config.value = await fetchConfig()
})Opción 2: Guarda la función de parada y llámala manualmente al desmontar.
let stopWatcher: (() => void) | null = null
onMounted(async () => {
await loadData()
stopWatcher = watch(data, (newVal) => {
processData(newVal)
})
})
onUnmounted(() => {
stopWatcher?.()
})La primera opción es casi siempre mejor. Si puedes reestructurar la lógica para que el watcher se cree de forma síncrona y la condición asíncrona se compruebe dentro del callback, evitas por completo la limpieza manual.
Ver también: ¿Por qué mi watchEffect pierde dependencias después de un await? · ¿Qué es effectScope y cuándo lo usarías?