Почему 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 setup
Vue сам очищаетwatch
, но вsetup
его нужно вручную отключать. - В Nuxt можно использовать
runWithContext
илиcallWithNuxt
. asyncContext
помогает, но увеличивает нагрузку на память.- Можно выполнять код до
await
, чтобы избежать потери контекста.
Знание этих нюансов поможет избежать утечек памяти и ошибок в Vue и Nuxt.
Добавить комментарий