Перевод публикуется с сокращениями, автор оригинальной статьи Alexey Kuznetsov.
Есть два созданных специально для этой статьи репозитория (для vue2 и vue3):
(Deep) наблюдатели за объектами
Правило простое: не используйте глубокий модификатор и не следите за переменными отличного от примитивного типа. Рассмотрим некоторые элементы массива из хранилища vuex, каждый из которых может иметь статус checked. Сохраним отдельно связанное с клиентом свойство IsChecked. Предположим, что есть геттер, который объединяет данные элемента и свойство IsChecked:
Элементы могут быть переупорядочены, и полученный порядок должен быть отправлен в бекенд:
Затем checking/unchecking элемента организует неправильное срабатывание вызова itemIds watcher. Это происходит, поскольку каждый раз, когда элемент становится checked, геттер создает новый объект для каждого элемента. По этой причине произойдет вычисление itemIds для пересоздания нового массива, несмотря на то, что он уже имеет те же значения в том же порядке: [1, 2, 3] !== [1, 2, 3].
Например, если нужно пронаблюдать за массивом элементов со свойствами {id, name, userId}, можно поступить следующим образом:
Очевидно, что более точное условие для запуска наблюдателя дает более точный триггер. С этой точки зрения, глубокий наблюдатель хуже, чем наблюдатель обычных объектов. Использование глубокого модификатора – явный признак того, что разработчик не заботится об объектах, за которыми наблюдает.
Демонстрационные материалы для vue2 и vue3.
Ограничение реактивности с помощью Object.freeze
Этот совет эффективен для приложения vue2 и способен уменьшить использование памяти на 50%. Иногда vuex содержит много редко изменяющихся данных, особенно если там хранится кэш ответов api (актуально для приложений ssr). По умолчанию vue рекурсивно наблюдает за каждым свойством объекта. Иногда лучше потерять реактивность свойств объекта, сохранив вместо этого немного памяти:
Примеры для vue2 и vue3. Поскольку vue3 имеет другую систему реактивности, эффект такой оптимизации менее релевантен. Кроме того общее использование памяти в vue3 намного ниже, чем в vue2 (это 80 Мб для vue2 и 15 Мб для примеров vue3). К сожалению, у vuex4 есть проблемы с очисткой (вы можете увидеть это данном примере), но разработчики обещают исправить ситуацию.
Функциональные геттеры
Зачастую это упускается из виду в документации: функциональные геттеры не кэшируются. Этот код будет запускать state.items.find каждый раз, когда он вызывается:
Такой код будет создавать объект itemsByIds при первом вызове, а затем повторно использовать:
Распределение компонентов
Распределение компонентов обеспечивает мощный механизм, дающий точный контроль над степенью детализации обновления. Это основная связанная с производительностью функция. Рассмотрим следующий код:
Это демо-версия: vue2, vue3. Попробуйте переименовать, отметить или изменить порядок элементов. Любое предназначенное только для одного элемента обновление приведет к повторному отображению других элементов в списке. Причина в том, что компонент <Item> ссылается на объект extendedItemsByIds, который перестраивается при изменении любого свойства элемента.
В первоначальной структуре используется подход normalizr, а наш геттер extendedItems копирует свойства элемента вместо того, чтобы просто ссылаться на исходный объект. Давайте это исправим:
Этот код корректно работает для переименования элемента (примеры vue2, vue3), но checking/unchecking элемента по-прежнему приводит к повторному рендерингу каждого, а переупорядочение элементов работает в vue2, но не в vue3.
Последняя описанная ситуация – это предполагаемое поведение, но оно не задокументировано. Причина заключается в ссылке на scope-переменную (id) в обработчиках событий @set-checked и @rename. В vue3 scope-переменная в обработчике событий не может быть закэширована, что означает – каждое обновление создает новую функцию события, вызывающую обновление компонента. Это можно исправить, отправив подготовленное событие, содержащее scope-переменную (id в нашем случае). Действие демонстрируется в примере для vue3.
Еще один способ исправить вызванный ссылкой на scope-переменную рендеринг – указать все события, которые компонент может обработать в новом свойстве vue3, называемом emits. Наш компонент ItemWithRenameById2 может работать с двумя событиями: @set-checked и @rename. Если добавить оба emits: ['set-checked', 'rename'], то переупорядочение элементов начнет работать правильно. Вот подтверждение для vue3.
Последнее, что нужно исправить – это IsChecked. Он ссылается на весь массив $store.state.checkedIds (пытается найти там значение). Состояние элемента «checking» изменяет этот массив, поэтому каждый <Item> должен повторить поиск. Это можно исправить, отправив IsChecked как логическое свойство каждому компоненту <Item> вместо поиска значения внутри:
Примеры корректно работающего решения для vue2 и vue3.
Директива IntersectionObserver
Иногда DOM сам по себе большой и медленный. Можно использовать несколько методов для уменьшения его размера. Например, диаграмма Ганта вычисляет положение и размер каждого элемента, и легко пропустить элементы за пределами viewport-а. Есть один простой трюк IntersectionObserver для случая, когда размеры неизвестны. Кстати, vuetify имеет директиву v-intersect из коробки, но последняя создает отдельный экземпляр IntersectionObserver, поэтому она не подходит для случая, когда нужно мониторить много узлов.
Давайте рассмотрим примеры (vue2, vue3), которые мы собираемся оптимизировать. Есть 100 записей (только 10 отображены на экране). Каждая содержит тяжелый svg, который мигает каждые 500 мс. Вычислим задержку между расчетным и реальным миганием, создадим один экземпляр IntersectionObserver и пробросим его на каждый узел, который необходимо наблюдать:
Теперь мы знаем, какие записи не видны – нам нужно их упростить. Можно определить некоторую переменную в vue и заменить тяжелую часть упрощенным заполнителем. Важно понимать, что инициализация сложных компонентов – та еще задачка. Может случиться, что страница будет лагать во время быстрой прокрутки, потому что делается слишком много тяжелых инициализаций. Практические эксперименты говорят, что переключение на уровне css происходит быстро и незаметно. Можно просто использовать css display: none для каждого тяжелого svg за пределами viewport, что увеличит производительность:
Заключение
Мы рассмотрели методы оптимизации работы кода в приложениях Vue, которые можно и нужно применять в ваших текущих проектах. Правильная работа с памятью при использовании компонентов и увесистых элементах viewport-а значительно улучшит эффективность использования приложения и поднимет UX. Удачи в обучении!
Дополнительные материалы:
Комментарии