Производительность
Вопрос
Что назвать, если просят ускорить 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, либо не кэшируют этот экран.