Контекст в Vue: управление окружением компонента

от автора

в ,
Время чтения: 2 мин.

Почему 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 при размонтировании подписки продолжают существовать, создавая утечку памяти.

Решения

  1. Очищать watch вручную с onUnmounted:
export default defineComponent({
  setup() {
    const state = useState();
    const stopWatching = watch(() => state.item, () => {});
    
    onUnmounted(() => {
      stopWatching();
    });
  }
});
  1. Использовать watchEffect с onScopeDispose:
export default defineComponent({
  setup() {
    const state = useState();
    watchEffect((onCleanup) => {
      const stopWatching = watch(() => state.item, () => {});
      onCleanup(() => stopWatching());
    });
  }
});
  1. Использовать 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.


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Сколько будет 1 + 6?