Skip to Content
VueПроизводительность

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

Уровень: middle. Компилятор и границы реактивности — senior.

Вопрос

Что назвать, если просят ускорить Vue-приложение, и откуда в SPA берётся утечка памяти?

Короткий ответ

Middle обычно закрывает тему пятью приёмами.

  • Маршруты и тяжёлые компоненты грузят лениво: component: () => import('./View.vue') и defineAsyncComponent.
  • Частое переключение видимости — v-show. Редкое условие, когда узел дорого держать в DOM, — v-if.
  • Длинный список виртуализируют: в DOM только строки во вьюпорте.
  • Большой ответ API, который меняют целиком, кладут в shallowRef или shallowReactive, чтобы Vue не проксировал каждое поле.
  • Статику помечают v-once. Поддерево, которое зависит от пары значений, — v-memo="[dep]".

Слушатели window, setInterval, шину и экземпляры Chart.js, TinyMCE, Leaflet снимают в onUnmounted. Иначе страница не перезагружается часами, а объекты остаются живы.

Разбор

Компилятор шаблонов Vue 3 размечает динамику заранее, в отличие от полного сравнения дерева.

  • Patch flags. Динамический узел получает битовую маску: текст, класс, пропсы. Повторный патч смотрит флаг, а не все атрибуты подряд.
  • Static hoisting. Узел без реактивности поднимают из функции рендера и переиспользуют.
  • Кэш обработчиков. Инлайн @click="count++" компилятор стабилизирует сам, отдельный useCallback для этого не пишут.

v-memo пересобирает поддерево, только если изменилась зависимость из массива. На списке с выбранной строкой зависимость вида «этот id выбран» обновляет две строки, а не тысячу.

v-once рендерит узел один раз и дальше его игнорирует. Для куска, который иногда всё же меняется, он не подходит.

Утечки:

  • window.addEventListener без снятия держит функцию и замыкание на экземпляр. Пара — onUnmounted, либо useEventListener из VueUse.
  • setInterval без clearInterval тикает после ухода со страницы.
  • mitt и старая шина без off копят подписчиков.
  • Сторонний виджет на DOM-узле уничтожают его методом (destroy), а не только размонтированием Vue.

KeepAlive эту уборку откладывает: пока компонент в кэше, onUnmounted не вызывается. Таймер глушат в onDeactivated.

На что смотрят на senior

Профайлер важнее чеклиста. Лишний рендер чаще даёт широкий reactive-стор, который читает каждая строка, чем отсутствие v-memo. Стабильный ключ и узкий computed на видимые данные бьют ручной мемоизации.

shallowRef требует замены ссылки. Мутация поля внутри не разбудит шаблон. Это сознательный контракт, его проговаривают в коде стора.

Виртуализация ломает нативный скролл к индексу, высоту строки и a11y, если библиотека этого не умеет. Её берут, когда список измеряется сотнями и тысячами, не на двадцать карточек.

Типичные ошибки

  • Менять v-if на v-show у тяжёлого поддерева, которое почти никогда не видно.
  • Вешать v-memo без измерения и забывать зависимость, из-за которой строка должна обновиться.
  • Считать, что сборщик мусора сам снимет слушатель window.
  • Глубоко проксировать снимок на 5 МБ, который экран только показывает.

Задача

В setup висит window.addEventListener('resize', onResize). Пользователь ушёл со страницы, слушатель остался. Куда поставить снятие и почему KeepAlive это место обходит?

Показать ответ

Пара к addEventListener — removeEventListener в onUnmounted. Иначе замыкание держит экземпляр.

KeepAlive не размонтирует компонент, когда его убирают с экрана. onUnmounted не вызовется. Слушатель тогда снимают в onDeactivated и возвращают в onActivated, либо не кэшируют этот экран.

Last updated on