78
0
0
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
Назад

Методы оптимизации React-приложения (часть 2)

Время чтения 6 минут
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники
78
0
0
Нет времени читать?
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники

Привет, читатель! Меня зовут Пётр, сейчас я ведущий фронтенд-разработчик в билайне. Я писал код для фронтенда и бэкенда, десктопа и мобилок на разных языках и фреймворках, а в своих статьях делюсь накопленным за годы работы опытом. В предыдущей статье мы рассмотрели несколько приемов для улучшения производительности React-приложения. Продолжим эту тематику и добавим еще несколько приемов в ваш арсенал.

Методы оптимизации React-приложения (часть 2)

Говорим НЕТ индексам массивов в качестве ключей для списков

Когда необходимо отобразить список элементов в React, важно добавить уникальный ключ (key) для каждого элемента. Ключи позволяют отслеживать изменения в списке, так как дают возможность идентифицировать измененные, добавленные или удаленные элементы. Они обеспечивают стабильную идентичность для каждого элемента, что критически важно для оптимизации производительности.

В React алгоритм сравнения нод в виртуальном DOM-дереве обращает внимание на проп key и изменения остальных пропов в нем, а затем решает, что с ним делать. Иногда возникают проблемы при использовании индексов массивов, так как элемент в массиве может быть перемещен или удален. В случае с элементом в конце списка сложностей нет, однако, если изменение произошло в середине списка, возрастает нагрузка на движок и происходит задержка замены нод. Это особенно заметно при использовании виртуальных списков или при большом объеме данных.

Каждый раз, когда в список добавляется новый элемент, React по умолчанию выполняет обход как нового, так и старого списка, внося изменения только при необходимости. Например, элемент, который изначально имел ключ 1, из-за изменения индекса теперь будет иметь ключ 2. React воспринимает это как изменение всех компонентов и инициирует обновление для каждого элемента в списке. Такой ненужный рендеринг вместо обновления одного элемента может привести к снижению производительности.

Ключ должен быть уникальным среди всех элементов списка. Если разработчик не указывает ключ, React по умолчанию использует в качестве него индекс элемента в массиве. Мы можем использовать индекс как ключ в следующем сценарии:

  • Элементы списка статичны и не меняются со временем.
  • Список никогда не фильтруется.
  • Список не меняет порядок.
  • В список не добавляются и не удаляются элементы сверху или снизу.

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

Чтобы не ждать доработок бэкенда, есть быстрый способ решить задачу на стороне клиента — генератор уникального идентификатора. Есть готовые решения, например uuid из lodash или v4, но можно написать свой на основе timestamp полученного из new Date().

«Ленивая» загрузка компонентов

В процессе сборки (building) несколько файлов объединяется в один, чтобы приложению не приходилось загружать множество внешних ресурсов. Все ключевые компоненты и зависимости собираются в единый файл, который затем передается по сети для инициализации и запуска веб-приложения. Сетевых запросов становится меньше, но возникает другая проблема: растет размер этого объединенного файла, что, в свою очередь, требует большей пропускной способности сети. В результате приложение ждет загрузки, затем выполнения этого крупного файла, растет задержка передачи и, как следствие, время рендеринга приложения.

Для решения этой проблемы можно применить концепцию «Разделение кода» (Code Splitting). Эта методология поддерживается такими инструментами, как Webpack, которые позволяют создавать несколько пакетов для приложения. Эти пакеты могут динамически загружаться во время выполнения, что значительно улучшает производительность и сокращает время загрузки.

Динамическая загрузка во время выполнения позволяет сократить размер исходного создаваемого пакета. Мы можем стратегически планировать разбиение пакетов так, чтобы компоненты, которые не требуются на начальном этапе, загружались позже, по мере необходимости. Это не только уменьшает размер основного пакета, но и снижает время загрузки приложения. Для реализации этой функциональности мы используем компоненты Suspense и Lazy.

Мы можем «лениво» загружать компоненты по мере необходимости, при этом они будут частью отдельного чанка. Это значительно улучшает общую производительность приложения.

Преимущества такого подхода:

  1. Меньше размер основного пакета.
  2. Динамическая загрузка: отдельные запросы на компоненты выполняются по мере необходимости, что позволяет загружать небольшой файл пакета без значительных задержек.
  3. Оптимизация начальной загрузки: мы можем проанализировать приложение и определить, какие компоненты можно загружать позже, и таким образом ускорить запуск.

Используем React.Fragments вместо дополнительных тегов

Часто замечал, что по неопытности разработчик пытается обернуть две рядом стоящие ноды в один родительский элемент div, забывая про наличие фрагментов React.

Использование фрагментов позволяет сократить количество дополнительных тегов, которые добавляются, исключительно чтобы в компоненте React был общий родитель. При создании нового компонента каждый из них должен иметь единственный родительский элемент, так как два тега не могут находиться на одном уровне. Фрагменты позволяют обойти это ограничение, обеспечивая наличие общего родителя без лишних оберток. Можно использовать как <Fragment />, так и сокращенный вариант “<></>”.

А что делать в случае списков и уникальных ключей для них? Если вы думаете, что необходимо всё же оборачивать в какой-то элемент наподобие <div /> и задавать ему проп key, то это не так. Fragment принимает проп key и прекрасно с ним работает, так что даже для списков нам нет необходимости создавать лишние ноды.

Не используем инлайн-функции

При работе часто возникает соблазн в качестве пропа передать инлайн-функцию (или другой проп). Это такие функции или пропы, которые мы не выносим отдельно в код выше (хендлеры, константы), а создаем сразу при описании верстки в return (или функции render).

Выше инлайн-функцией считается та, что мы передаем в onClick компонента CardComponent. При использовании таких функций на каждый вызов рендеринга создается новый экземпляр. React сравнивает их с Virtual DOM, обнаруживает, что функция изменилась, из-за чего на этапе рендеринга происходит повторное связывание новой функции, а старый экземпляр становится кандидатом на сборку мусора. В результате прямое использование встроенных функций приводит к дополнительной нагрузке на сборщик мусора и увеличивает количество операций по привязке функций к DOM.

Вместо использования встроенной функции рекомендую определить ее внутри компонента и привязать к ней событие. Такой подход предотвращает создание нового экземпляра функции при каждом вызове render, что повышает производительность. В нашем случае мы можем просто использовать саму функцию doSomething или обернуть ее в хендлер, как показано ниже.

Но что делать, когда нам необходимо передавать что-либо в функцию вызова? Рассмотрим вариант, когда надо передать id элемента в функцию doSomething. Сразу появляется соблазн написать инлайн-функцию:

Однако это неверный ход. В таком случае следует вынести функцию в хендлер, которая будет находиться внутри функции map.

В свою очередь, это сигнализирует нам о том, что необходимо создать новый компонент и инкапсулировать логику передачи id в иной хендлер.

В коде выше мы не только устранили проблему с инлайн-функциями, но также:

  1. Убрали лишнее использование Fragment.
  2. Создали переиспользуемый элемент ListItem.
  3. Сделали код более читабельным и удобным.

В дополнение к вышесказанному стоит рассмотреть отдельный проп style. При использовании встроенных стилей браузер затрачивает значительно больше времени на выполнение скриптов и рендеринг. Это происходит потому, что для отображения всех стилей необходимо обработать множество CSS-свойств, передаваемых напрямую в виде инлайновых правил, что увеличивает время рендеринга компонентов. Лучшим вариантом для приложения будет использование CSS-файлов (модулей, препроцессоров и пр.) вместо инлайн-стилей.

Выносим объемные вычисления в Web Workers

JavaScript — однопоточный язык, то есть все синхронные операции выполняются в одном потоке. При визуализации веб-страницы одновременно выполняется множество задач: обработка взаимодействий с пользователем и данных ответов, управление элементами DOM, запуск анимаций и другие. 

Все эти процессы происходят в одном потоке, что может приводить к нагрузке и задержкам. В практике у многих случалось, что какая-либо операция могла «положить» страницу либо заставить интерфейс не откликаться на действия пользователя. В таких случаях Web Workers выполняют ресурсоемкие задачи в фоновом режиме и тем самым разгружают основной поток.

Рабочие потоки (Worker threads) — это фоновые потоки, которые позволяют выполнять несколько скриптов и запускать JavaScript без блокировки основного потока. Длительные и ресурсоемкие задачи, которые нагружают процессор, можно перенести в отдельные рабочие потоки. Они выполняются в изолированной среде и взаимодействуют с основным потоком с помощью механизма межпроцессного обмена сообщениями. Тем временем основной поток остается свободным для обработки рендеринга и управления DOM, обеспечивая плавную работу интерфейса.

Подробнее про использование Web Workers можно почитать на MDN или в доке.

Оптимизация React-приложения — важный шаг на пути к созданию быстрого и отзывчивого интерфейса. Применение описанных в статье подходов поможет значительно улучшить производительность и пользовательский опыт. Спасибо за ваше внимание и интерес к теме! Надеюсь, эта статья была для вас полезной и вдохновит на дальнейшее совершенствование ваших проектов. До новых встреч!

Комментарии0
Тоже интересно
Комментировать
Поделиться
Скопировать ссылку
Telegram
WhatsApp
Vkontakte
Одноклассники