Производительность при изменении видимости

В приложениях на Vue часто возникает необходимость динамически управлять видимостью компонентов и элементов. Изменение видимости может происходать через условные рендеры (v-if), переключение классов (v-show) или манипуляцию стилями через привязку к style. При тестировании таких сценариев важно оценивать не только корректность отображения, но и влияние на производительность и поведение виртуального DOM.

Отличия v-if и v-show

v-if полностью монтирует или размонтирует компонент в DOM в зависимости от условия. Это значит:

  • При переключении видимости создаётся или уничтожается экземпляр компонента.
  • Инициализация v-if может включать вычисляемые свойства, watchers и lifecycle hooks (created, mounted), что требует дополнительных ресурсов.
  • Подходит для редких изменений видимости, когда важно минимизировать количество элементов в DOM.

v-show просто меняет CSS-свойство display:

  • Компонент остаётся в DOM, меняется только видимость.
  • Lifecycle hooks не вызываются повторно.
  • Подходит для частых переключений, так как не требует повторного монтирования.

Тестирование условной отрисовки с v-if

При тестировании компонентов с v-if критично проверять не только наличие элемента, но и его корректное монтирование и размонтирование.

import { mount } from '@vue/test-utils';
import ToggleComponent from '@/components/ToggleComponent.vue';

describe('ToggleComponent', () => {
  it('должен монтировать компонент при isVisible=true', async () => {
    const wrapper = mount(ToggleComponent, {
      data() {
        return { isVisible: false };
      },
    });

    expect(wrapper.find('.content').exists()).toBe(false);

    await wrapper.setData({ isVisible: true });
    
    expect(wrapper.find('.content').exists()).toBe(true);
  });

  it('должен размонтировать компонент при isVisible=false', async () => {
    const wrapper = mount(ToggleComponent, {
      data() {
        return { isVisible: true };
      },
    });

    await wrapper.setData({ isVisible: false });

    expect(wrapper.find('.content').exists()).toBe(false);
  });
});

Ключевой момент: v-if полностью удаляет элемент из DOM, поэтому любые привязанные события или внутренние состояния компонента исчезают при скрытии. Тесты должны учитывать это поведение, особенно если компонент содержит сложную логику или сторонние зависимости.

Тестирование с v-show

Для v-show проверка видимости сводится к проверке CSS-свойства display:

it('должен показывать и скрывать элемент через v-show', async () => {
  const wrapper = mount(ToggleComponent, {
    data() {
      return { isVisible: false };
    },
  });

  const content = wrapper.find('.content');
  expect(content.isVisible()).toBe(false);

  await wrapper.setData({ isVisible: true });
  expect(content.isVisible()).toBe(true);
});

Особенность v-show: компонент не размонтируется, поэтому его состояние сохраняется. Это влияет на производительность тестов: переключение видимости происходит быстрее, но важно помнить, что скрытые элементы продолжают существовать в DOM и могут потреблять ресурсы.

Измерение производительности в тестах

В больших приложениях важно контролировать накладные расходы при динамическом изменении видимости. Для этого можно использовать следующие подходы:

  1. Замеры времени выполнения:

    const start = performance.now();
    await wrapper.setData({ isVisible: true });
    const end = performance.now();
    console.log(`Время обновления: ${end - start} мс`);
  2. Проверка количества монтирований и размонтирований:

    • Для компонентов с v-if можно отслеживать вызовы lifecycle hooks:

      const createdSpy = jest.fn();
      const wrapper = mount(ComponentWithHook, {
        created: createdSpy
      });

      Это помогает выявить дорогостоящие повторные монтирования.

  3. Минимизация повторного рендеринга:

    • Использовать v-show для часто переключающихся элементов.
    • Кэшировать компоненты с v-if через keep-alive, если требуется сохранение состояния.

Отслеживание побочных эффектов

Скрытие компонента может влиять на поведение watchers, асинхронных операций и глобальных состояний. Примеры:

  • v-if удаляет компонент, прекращая выполнение внутренних таймеров и отписку от событий.
  • v-show сохраняет компонент активным, что может приводить к неожиданным коллизиям при параллельных обновлениях данных.

Тесты должны симулировать повторное скрытие и показ компонента и проверять:

  • сохранность состояния (v-show)
  • правильное пересоздание (v-if)
  • корректность вызова методов и watchers

Использование nextTick для синхронизации

Любое изменение видимости в Vue может запускать асинхронный процесс рендера. Для корректного тестирования необходимо использовать await nextTick():

import { nextTick } from 'vue';

await wrapper.setData({ isVisible: true });
await nextTick();
expect(wrapper.find('.content').exists()).toBe(true);

Это гарантирует, что DOM полностью обновился перед проверкой, особенно при комплексных компонентах с дочерними элементами и computed-свойствами.

Рекомендации по проектированию тестов

  • Для v-if тестировать и наличие, и отсутствие элементов, а также инициализацию и размонтирование.
  • Для v-show концентрироваться на проверке видимости через CSS и сохранении состояния.
  • Использовать замеры производительности для компонентов с частыми переключениями видимости.
  • Проверять побочные эффекты watchers, таймеров и асинхронных операций.
  • Применять nextTick после изменения данных или пропсов, влияющих на видимость.

Эти методы обеспечивают полное покрытие сценариев изменения видимости и позволяют выявлять потенциальные проблемы с производительностью и управлением состоянием в Vue-приложениях.