Vue

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

Таблица заказов на 5000 строк подгружает данные за 80 миллисекунд, но при наборе текста в фильтре каждый символ подвешивает интерфейс на четверть секунды. Профайлер Vue DevTools показывает, что один keypress перерисовывает все 5000 строк, хотя на экране помещается 30. Дело не в данных и не в сети: реактивность отслеживает глубокий объект целиком, шаблон зовёт метод-форматтер на каждой строке заново, а в DOM живёт пять тысяч узлов разом. Этот урок про инструменты, которые убирают лишнюю работу: shallowRef и markRaw, v-once и v-memo, виртуальный скролл и computed.

  • Vue DevTools (вкладка Performance): профилирует длительность и причину каждого компонентного ре-рендера
  • Линии Inertia и админки на Vue: тяжёлые таблицы на десятки тысяч строк держатся на виртуальном скролле
  • Графические редакторы и карты: экземпляры сторонних библиотек оборачивают в markRaw, чтобы не делать их реактивными
  • Дашборды реального времени: shallowRef для больших снапшотов данных, которые меняются целиком, а не по полю
  • Чаты и ленты: v-memo на элементах списка пропускает перерисовку строк, чьи данные не изменились

Предварительные знания

  • Понимание ref и reactive и того, что глубокая реактивность отслеживает вложенные свойства
  • Знакомство с shallowRef и markRaw на уровне идеи
  • Базовая работа с шаблонами, директивами и computed-свойствами

Дисциплина shallowRef и markRaw

По умолчанию reactive и ref делают реактивность глубокой: Vue рекурсивно оборачивает в прокси каждое вложенное свойство, чтобы отследить любое изменение. Для небольшого объекта это незаметно. Но если в состоянии лежит снапшот из десяти тысяч элементов или экземпляр графической библиотеки с сотней внутренних полей, рекурсивное оборачивание само по себе стоит времени и памяти, а отслеживание изменений, которые никого не интересуют, добавляет накладные расходы на каждое обновление.

shallowRef отслеживает только присваивание самого .value. Вложенные поля не оборачиваются в прокси, поэтому большой массив или объект не платит за глубокую реактивность. Это идеальный выбор для данных, которые приходят и заменяются целиком: ответ API, кадр данных, снапшот состояния. Когда нужно мутировать вложенное поле и всё же уведомить шаблон, используется triggerRef.

markRaw помечает объект флагом, который запрещает Vue делать его реактивным. Это спасает от двух бед: лишнего оборачивания тяжёлого экземпляра и поломки самой библиотеки, которая может хранить внутри ссылки на DOM-узлы или приватные поля, не переживающие проксирование. Экземпляры карт, чартов, редакторов и WebGL-сцен почти всегда оборачивают в markRaw.

В состоянии лежит экземпляр библиотеки карт Leaflet с сотнями внутренних свойств и ссылками на DOM. Как его хранить?

Пропуск перерисовки: v-once, v-memo, computed

Самая дешёвая работа это та, которую не делают. Vue даёт три инструмента, чтобы пропустить ненужную перерисовку. v-once рендерит фрагмент ровно один раз и навсегда замораживает его. v-memo пропускает обновление поддерева, пока значения в его массиве зависимостей не изменились. computed кеширует результат и пересчитывает его только при изменении зависимостей, в отличие от метода, который выполняется на каждый рендер.

v-memo принимает массив значений. Если все они совпали с предыдущим рендером, Vue полностью пропускает обновление этого элемента и его детей. На большом списке это убирает основную стоимость: при выделении одной строки перерисовывается только она и предыдущая выделенная, а не весь список. Важно перечислить в массиве все значения, от которых реально зависит разметка, иначе обновление пропустится, когда оно было нужно.

v-memo легко применить неправильно. Если в массиве зависимостей забыть значение, от которого зависит шаблон, элемент перестанет обновляться при его изменении и появится визуальный баг, который трудно отследить. v-memo стоит вводить только на доказанно горячих списках и всегда перечислять полный набор зависимостей.

Почему для итоговой суммы в шаблоне предпочтительнее computed, а не метод?

Большие списки и измерение

Даже идеально оптимизированный компонент строки не спасёт, если в DOM одновременно живут десять тысяч узлов. Браузер тратит время на layout и paint каждого, а память растёт линейно. Виртуальный скролл решает это, держа в DOM только видимые элементы плюс небольшой буфер. При прокрутке узлы переиспользуются: то, что ушло за край экрана, перерисовывается под новые данные снизу. Пользователь видит полный список, но в DOM лежат три десятка строк, а не десять тысяч.

Любую оптимизацию начинают не с догадки, а с измерения. Вкладка Performance в Vue DevTools записывает сессию взаимодействия и показывает каждый компонентный ре-рендер: его длительность, частоту и причину. Если один keypress перерисовывает поддерево из тысяч компонентов, это видно сразу, и понятно, куда ставить v-memo или виртуальный скролл. Без профайлера оптимизация превращается в стрельбу вслепую, когда время тратится на компоненты, которые и так дёшевы.

СимптомВероятная причинаИнструмент
Один клик перерисовывает весь списокНет мемоизации строкv-memo по ключевым полям
Скролл по 20k строкам тормозитВсе узлы в DOM одновременноВиртуальный скролл
Ввод в фильтр подвешивает UIМетод-форматтер на каждой строкеcomputed вместо метода
Загрузка большого ответа лагаетГлубокое проксирование снапшотаshallowRef

Сначала измеряют, потом оптимизируют. Преждевременная оптимизация добавляет v-memo и markRaw туда, где компонент и так перерисовывается за доли миллисекунды, усложняя код без выигрыша. Вкладка Performance показывает реальные горячие точки, и работа идёт только по ним.

Список из 20000 строк тормозит при скролле, хотя каждая строка мемоизирована через v-memo. Что устранит главную причину?

Связь с другими темами

Урок опирается на реактивность и продолжает тему рендеринга:

  • Глубокая реактивность — Лишние ре-рендеры начинаются с того, что реактивность отследила слишком много зависимостей
  • shallowRef и markRaw — Поверхностная реактивность и исключение объектов это первый рычаг производительности
  • Гидрация и гибридный рендеринг — Производительность после гидрации определяется стоимостью обновлений на клиенте

Итог

  • shallowRef делает реактивным только присваивание самого значения, а не вложенные поля, что выгодно для крупных снапшотов, меняющихся целиком
  • markRaw помечает объект как нереактивный навсегда, что нужно для экземпляров сторонних библиотек, которые незачем оборачивать в прокси
  • v-once рендерит фрагмент один раз и больше никогда не обновляет его, а v-memo пропускает обновление поддерева, пока массив зависимостей не изменился
  • Виртуальный скролл держит в DOM только видимые элементы вместо тысяч узлов, что снимает основную стоимость больших списков
  • computed кешируется по зависимостям, а метод в шаблоне выполняется на каждый рендер, поэтому для вычисляемых значений выбирается computed
  • Вкладка Performance в Vue DevTools показывает, какой компонент перерисовывается, как часто и почему, и это основа любой оптимизации

Связанные уроки

  • vue-37-hydration-rendering — Гидрация задаёт стартовую цену интерактивности, а этот урок снижает цену каждого последующего обновления
  • vue-18-reactivity-deep — Оптимизация ре-рендеров опирается на понимание того, как реактивность отслеживает зависимости
Производительность Vue

0

1

Войти