← Назад к вопросам
Будет ли что-либо происходить с объектом созданным в mounted во Vue?
2.0 Middle🔥 121 комментариев
#Vue.js
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Жизненный цикл объектов в Vue mounted: утечки памяти и очистка
Когда ты создаёшь объект в хуке mounted во Vue, нужно понимать, что с ним происходит после размонтирования компонента. Это критично для предотвращения утечек памяти.
Что происходит с объектом после unmount?
// ❌ Опасно: утечка памяти
export default {
mounted() {
// Создаём объект
this.heavyObject = {
data: new Array(1000000).fill("data"),
timerId: null,
};
// Запускаем интервал (НЕ очищаем!)
this.timerId = setInterval(() => {
console.log("Doing something...");
}, 1000);
},
};
// Когда компонент размонтируется:
// 1. this.heavyObject всё ещё в памяти
// 2. Интервал продолжает работать (утечка!)
// 3. Если интервал обращается к компоненту — ОШИБКА
Правильный способ: очистка в unmounted
// ✅ Правильно: очищаем ресурсы
export default {
data() {
return {
timerId: null,
heavyObject: null,
};
},
mounted() {
// Создаём объект
this.heavyObject = {
data: new Array(1000000).fill("data"),
};
// Запускаем интервал
this.timerId = setInterval(() => {
console.log("Doing something...");
}, 1000);
},
unmounted() {
// Очищаем интервал
if (this.timerId) {
clearInterval(this.timerId);
}
// Очищаем тяжёлый объект
this.heavyObject = null;
},
};
Практический пример: WebSocket
// ❌ Плохо: WebSocket остаётся открытым
export default {
mounted() {
this.ws = new WebSocket("ws://api.example.com");
this.ws.onmessage = (event) => {
this.data = event.data;
};
},
// WebSocket остаётся открытым, даже после размонтирования!
};
// ✅ Хорошо: закрываем WebSocket
export default {
data() {
return {
ws: null,
data: null,
};
},
mounted() {
this.ws = new WebSocket("ws://api.example.com");
this.ws.onmessage = (event) => {
this.data = event.data;
};
},
unmounted() {
if (this.ws) {
this.ws.close();
this.ws = null;
}
},
};
Практический пример: EventListener
// ❌ Плохо: слушатель остаётся
export default {
mounted() {
window.addEventListener("resize", this.handleResize);
},
methods: {
handleResize() {
this.windowWidth = window.innerWidth;
},
},
// Слушатель остаётся активным!
};
// ✅ Хорошо: удаляем слушатель
export default {
data() {
return {
windowWidth: window.innerWidth,
};
},
mounted() {
window.addEventListener("resize", this.handleResize);
},
unmounted() {
window.removeEventListener("resize", this.handleResize);
},
methods: {
handleResize() {
this.windowWidth = window.innerWidth;
},
},
};
Обработчик mousemove
// ❌ Утечка памяти
export default {
mounted() {
// На каждое движение мыши создаётся новая функция!
document.addEventListener("mousemove", () => {
this.mouseX = event.clientX;
this.mouseY = event.clientY;
});
},
};
// ✅ Правильно: используем метод компонента
export default {
data() {
return {
mouseX: 0,
mouseY: 0,
};
},
mounted() {
document.addEventListener("mousemove", this.handleMouseMove);
},
unmounted() {
document.removeEventListener("mousemove", this.handleMouseMove);
},
methods: {
handleMouseMove(event) {
this.mouseX = event.clientX;
this.mouseY = event.clientY;
},
},
};
Таймеры и задержки
// ❌ Плохо: setTimeout продолжит работать
export default {
mounted() {
setTimeout(() => {
this.doSomething(); // ОШИБКА: this уже не существует
}, 5000);
},
methods: {
doSomething() {
console.log("Done");
},
},
};
// ✅ Хорошо: отменяем timeout
export default {
data() {
return {
timeoutId: null,
};
},
mounted() {
this.timeoutId = setTimeout(() => {
this.doSomething();
}, 5000);
},
unmounted() {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
},
methods: {
doSomething() {
console.log("Done");
},
},
};
Подписки на внешние источники (Observable)
// ❌ Плохо: подписка не отменяется
import { observable$ } from "some-library";
export default {
data() {
return {
data: null,
subscription: null,
};
},
mounted() {
this.subscription = observable$.subscribe(data => {
this.data = data;
});
},
// Подписка продолжит работать!
};
// ✅ Хорошо: отменяем подписку
export default {
data() {
return {
data: null,
subscription: null,
};
},
mounted() {
this.subscription = observable$.subscribe(data => {
this.data = data;
});
},
unmounted() {
if (this.subscription) {
this.subscription.unsubscribe();
}
},
};
Полный пример: сложный компонент с несколькими ресурсами
export default {
data() {
return {
// Таймеры
intervalId: null,
timeoutId: null,
// Соединения
ws: null,
subscription: null,
// Данные
data: null,
mouseX: 0,
mouseY: 0,
};
},
mounted() {
// Интервал для обновления данных
this.intervalId = setInterval(() => {
this.fetchData();
}, 5000);
// WebSocket для реалтайм данных
this.ws = new WebSocket("ws://api.example.com");
this.ws.onmessage = (event) => {
this.data = event.data;
};
// Слушатель движения мыши
document.addEventListener("mousemove", this.handleMouseMove);
// Подписка на Observable
this.subscription = dataStream$.subscribe(data => {
this.data = data;
});
// Отложенная операция
this.timeoutId = setTimeout(() => {
this.loadMoreData();
}, 10000);
},
unmounted() {
// Очищаем интервал
if (this.intervalId) {
clearInterval(this.intervalId);
}
// Очищаем timeout
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
// Закрываем WebSocket
if (this.ws) {
this.ws.close();
this.ws = null;
}
// Отменяем подписку
if (this.subscription) {
this.subscription.unsubscribe();
}
// Удаляем слушатель
document.removeEventListener("mousemove", this.handleMouseMove);
// Очищаем данные
this.data = null;
},
methods: {
handleMouseMove(event) {
this.mouseX = event.clientX;
this.mouseY = event.clientY;
},
async fetchData() {
try {
const response = await fetch("/api/data");
this.data = await response.json();
} catch (error) {
console.error("Failed to fetch data:", error);
}
},
loadMoreData() {
// Загружаем дополнительные данные
},
},
};
Использование composables для чистоты кода
// hooks/useMouseTracker.js
import { ref, onMounted, onUnmounted } from "vue";
export function useMouseTracker() {
const x = ref(0);
const y = ref(0);
const handleMouseMove = (event) => {
x.value = event.clientX;
y.value = event.clientY;
};
onMounted(() => {
document.addEventListener("mousemove", handleMouseMove);
});
onUnmounted(() => {
document.removeEventListener("mousemove", handleMouseMove);
});
return { x, y };
}
// Component.vue
<script setup>
import { useMouseTracker } from "@/hooks/useMouseTracker";
const { x, y } = useMouseTracker();
</script>
<template>
<div>
Mouse: {{ x }}, {{ y }}
</div>
</template>
Частая ошибка: забыли про стрелочные функции
// ❌ Плохо: стрелочная функция в addEventListener
mounted() {
// Каждый раз новая функция — removeEventListener не сработает!
window.addEventListener("resize", () => {
this.handleResize();
});
},
unmounted() {
// Эта функция не будет удалена, т.к. это другой объект
window.removeEventListener("resize", () => {
this.handleResize();
});
},
// ✅ Правильно: сохраняем ссылку на метод
mounted() {
window.addEventListener("resize", this.handleResize);
},
unmounted() {
window.removeEventListener("resize", this.handleResize);
},
methods: {
handleResize() {
// ...
},
},
Чеклист очистки ресурсов
Когда создаёшь что-то в mounted, помни про unmounted:
- setInterval -> clearInterval
- setTimeout -> clearTimeout
- addEventListener -> removeEventListener
- WebSocket -> ws.close()
- Observable.subscribe -> subscription.unsubscribe()
- Promise -> отмени, если возможно
- requestAnimationFrame -> cancelAnimationFrame
Заключение
Объекты, созданные в mounted:
- Остаются в памяти после unmount, если не очищены
- Могут вызвать ошибки, если пытаются получить доступ к компоненту
- Приводят к утечкам памяти и деградации производительности
- Должны быть очищены в unmounted
Всегда помни про cleanup!