Почему watch продолжает работать после ухода со страницы?
Вы могли заметить, что watch остаётся активным после ухода со страницы, что может привести к утечкам памяти. Это связано с тем, что watch создаёт подписку, которая не очищается автоматически. Рассмотрим способы решения этой проблемы.
getCurrentInstance
Эта функция возвращает активный компонент и его свойства:
uid— уникальный идентификатор;parent,root— родительский и корневой компонент;props,slots,isMountedи другие.
Внутри getCurrentInstance есть proxy, аналог this, содержащий методы, слоты и данные компонента.
Пример использования:
export function assignDataToInstance<T extends IDataPartial = IDataPartial>(data: Ref<T>) {
const instance = getCurrentInstance()?.proxy;
if (!instance) return;
instance.__customData = data;
}
export function retrieveDataFromInstance<T extends IDataPartial = IDataPartial>(): Ref<T> | null {
const proxy = getCurrentInstance()?.proxy;
return proxy?.__customData as Ref<T> ?? null;
}Контекст, который был утерян, уже не восстановить.
getCurrentScope
Эта функция отслеживает реактивные зависимости (например, watchEffect). Vue автоматически очищает их при размонтировании, предотвращая утечки памяти.
Метод run позволяет восстановить окружение компонента, а хук onScopeDispose используется для очистки.
Потеря контекста: причины и последствия
Часто контекст теряется из-за асинхронного кода (await). Vue отслеживает компонент до завершения setup, но await приводит к потере proxy.
Проблема:
export default defineComponent({
async setup() {
const state = useState();
const stopWatching = watch(() => state.item, () => {});
state.data = await (await fetch('https://foostack.ru')).json();
const stopWatchingData = watch(() => state.data, () => {});
onUnmounted(() => {
stopWatching();
stopWatchingData();
});
}
});Без вызова stopWatching при размонтировании подписки продолжают существовать, создавая утечку памяти.
Решения
- Очищать
watchвручную сonUnmounted:
export default defineComponent({
setup() {
const state = useState();
const stopWatching = watch(() => state.item, () => {});
onUnmounted(() => {
stopWatching();
});
}
});- Использовать
watchEffectсonScopeDispose:
export default defineComponent({
setup() {
const state = useState();
watchEffect((onCleanup) => {
const stopWatching = watch(() => state.item, () => {});
onCleanup(() => stopWatching());
});
}
});- Использовать
script setup, где Vue автоматически очищаетwatch:
const state = useState();
const stopWatching = watch(() => state.item, () => {});
onUnmounted(() => {
stopWatching();
});Можно писать код до вызова async методов
Перед вызовом await можно выполнить любые операции, чтобы избежать потери контекста. Например:
export default defineComponent({
async setup() {
const state = useState();
const stopWatching = watch(() => state.item, () => {});
// Можно выполнить логику до вызова await
const tempData = { active: true };
console.log(tempData);
state.data = await (await fetch('https://foostack.ru')).json();
onUnmounted(() => {
stopWatching();
});
}
});Это позволяет избежать неожиданных потерь контекста.
Контекст в SSR
Использование глобальных ref и computed в composables может привести к утечке данных пользователей. Решение — использовать store/provide вместо глобальных ref.
Контекст в Nuxt
Nuxt использует контекстные методы (useCookie, useHead и др.). Потеря контекста в useAsyncData после await может вызвать ошибки на SSR.
Исправление:
const app = useNuxtApp();
await useAsyncData(async () => {});
await useAsyncData(async () => {
await someProcess();
app.runWithContext(() => useCustomComposable(params));
});Async Context в Nuxt 3
Nuxt 3 предлагает experimental.asyncContext, сохраняющий контекст после await.
export default defineNuxtConfig({
experimental: {
asyncContext: true,
},
});Это решает проблему, но увеличивает потребление памяти.
Выводы
getCurrentInstanceхранит информацию о компоненте.getCurrentScopeуправляет реактивностью.- Контекст теряется после
await, если его не восстановить. - В
script setupVue сам очищаетwatch, но вsetupего нужно вручную отключать. - В Nuxt можно использовать
runWithContextилиcallWithNuxt. asyncContextпомогает, но увеличивает нагрузку на память.- Можно выполнять код до
await, чтобы избежать потери контекста.
Знание этих нюансов поможет избежать утечек памяти и ошибок в Vue и Nuxt.


Добавить комментарий