Вы планируете перенести большое одностраничное приложение AngularJS на React? Если это так, вам может быть интересно, какой прирост производительности вы собираетесь получить с помощью React и как код будет трансформироваться (с библиотеками управления состоянием Redux или Mobx).

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

Сначала я рассмотрю профили производительности и памяти для различных сценариев пользовательского интерфейса, реализованных с использованием AngularJS, React / Redux и React / Mobx. Мы сравним и сопоставим производительность этих фреймворков по таким параметрам, как время выполнения скрипта, количество кадров в секунду и usedJSHeapSize для каждого сценария.

Я предоставил ссылки на тестовые страницы и исходный код, чтобы вы могли опробовать эти сценарии и просмотреть код, чтобы почувствовать конструкции, которые React (с Redux или Mobx) внесет в таблицу.

Настройка теста производительности

Чтобы оценить производительность AngularJS и React, я создал тестовое приложение - панель управления биржевыми тикерами. Это приложение показывает список акций и некоторые элементы управления для автоматизации действий при тестировании пользовательского интерфейса. Для каждой акции приложение показывает символ тикера, название компании, название сектора, текущую цену, объем и простые скользящие средние (10 дней, 50 дней и 200 дней), а также визуальный индикатор, показывающий, пошла цена вверх или вниз. Набор тестовых данных состоит из 5000 биржевых тикеров и загружается во время загрузки страницы через тег скрипта.

Я создал три версии этого приложения, используя AngularJS, React / Redux и React / Mobx. Это позволяет нам легко сравнивать показатели производительности для каждого сценария на разных платформах.

Сценарии тестирования

  • Переключение представлений
    Мы просматриваем список из 5000 тикеров акций, показывающий 150 тикеров за раз каждые 0,5 секунды. Этот сценарий измеряет, насколько быстро платформа может обновлять представление при изменении видимой модели данных коллекции.
    Пример использования в реальном мире: изменение маршрута, перелистывание списка, виртуальная прокрутка и т. д.
  • Добавление тикеров
    Мы добавляем 50 тикеров в видимую коллекцию каждые 100 мс, пока не отобразим всю коллекцию из 5000 тикеров. Этот сценарий измеряет, насколько быстро платформа может создавать новые элементы. Отображение 5000 тикеров - нереальный сценарий, но мы можем визуализировать пределы, в которых что-то развалится с каждым фреймворком.
    Пример использования в реальном мире: бесконечная прокрутка в стиле Pinterest, где новые элементы пользовательского интерфейса добавляются в DOM по мере прокрутки пользователя.
  • Быстрое обновление цены / объема
    Мы отображаем 1500 тикеров и начинаем обновлять данные о цене / объеме для случайных тикеров каждые 10 мс. Этот сценарий измеряет, насколько быстро платформы могут применять частичные обновления к пользовательскому интерфейсу.
    Пример использования в реальном мире: обновления индикаторов присутствия, лайков, ретвитов, аплодисментов, курсов акций и т. д.
  • Удаление тикеров
    Сначала мы добавим все 5000 тикеров, а затем начнем удалять 50 тикеров из видимой коллекции каждые 100 мс.

Ссылки на тестовые страницы и источник

Все примеры написаны на Typescript, а компиляция / сборка выполняется с помощью Webpack. На странице Readme для исходного URL-адреса перечислены инструкции по созданию и запуску приложений.

Прежде чем мы начнем…

  • Все указанные ниже показатели измерены на Win10 / Intel Xeon E5 @ 2,4 ГГц, 6 ядер, настольный компьютер 32 ГБ с браузером Chrome v60. Цифры будут меняться на разных машинах / браузерах.
  • Чтобы увидеть точные данные о динамической памяти на тестовых страницах, откройте Chrome с флагом ‘--enable-precision-memory-info’.
  • React - это скорее библиотека, чем полноценный фреймворк вроде AngularJS. В этом посте для простоты я использовал термин «фреймворк».
  • На тестовых страницах размер динамической кучи JavaScript отображается как память.
    О размере кучи JavaScript: в Chrome TaskManager

В Chrome TaskManager Столбец« Память представляет внутреннюю память. Узлы DOM хранятся в собственной памяти. Если это значение увеличивается, создаются узлы DOM. Столбец Память JavaScript представляет кучу JS. Этот столбец содержит два значения. Интересующее вас значение - это действующее число (число в скобках). Текущее число показывает, сколько памяти используют доступные объекты на вашей странице. Если это число увеличивается, либо создаются новые объекты, либо существующие объекты растут. От устранения проблем с памятью Кейси Басков

  • О кадрах в секунду:

Сегодня большинство устройств обновляют свои экраны 60 раз в секунду. Если выполняется анимация или переход, или пользователь прокручивает страницы, браузер должен соответствовать частоте обновления устройства и отображать одно новое изображение или кадр для каждого из этих обновлений экрана. Каждый из этих кадров имеет бюджет чуть более 16 мс (1 секунда / 60 = 16,66 мс). На самом деле, однако, браузеру нужно выполнять служебную работу, поэтому вся ваша работа должна быть завершена за 10 мс. Когда вы не можете уложиться в этот бюджет, частота кадров падает, и контент на экране дрожит. Это часто называют джанком, и это негативно влияет на восприятие пользователем. Из рендеринга Пола Льюиса

DOM - компоненты AngularJS и компоненты React

Директивы (или компоненты) AngularJS создают вокруг шаблона дополнительный элемент-оболочку. Для простых представлений это не проблема. Однако в сложных представлениях, содержащих большое количество директив (особенно, когда они повторяются в ng-repeat), все дополнительные элементы будут составлять общий размер дерева DOM, что может повлиять на память, производительность селектора и т. Д.

Хотя вы можете установить свойство replace = true, чтобы отключить рендеринг элемента оболочки, это вызывает множество проблем и в настоящее время помечено как устаревшее.

Вот визуализированный HTML-код для компонента тикера в AngularJS:

Вот визуализированный HTML-код для аналогичного компонента тикера в React:

В нашем конкретном примере AngularJS создал дополнительные 1400 узлов DOM по сравнению с React для рендеринга того же количества тикеров (200).

Сценарий 1 - переключение представлений

Мы перемещаемся по списку из 5000 тикеров, показывая 150 тикеров за раз каждые 0,5 секунды.

На приведенной ниже диаграмме показано время выполнения скрипта для каждого обновления на временной шкале производительности Chrome. AngularJS постоянно требовал ›200 мс, чтобы удалить существующие 150 тикеров и показать новые. В то время как React / Redux проделал ту же работу в пределах 90–100 мс (вдвое меньше времени по сравнению с ng1). Версия React / Mobx заняла немного больше времени, чем версия Redux, но не далеко от этого.

На приведенной ниже диаграмме показано количество кадров в секунду (fps) при обновлении. Версии Redux и Mobx оставались на уровне 45 кадров в секунду, тогда как AngularJS оставались на уровне 30 кадров в секунду в течение всего прогона.

Паузы памяти и сборки мусора

На диаграмме ниже показан размер кучи JavaScript (usedJSHeapSize), измеренный во время обновления. Обе версии AngularJS и Mobx показали ступенчатую схему потребления памяти, что указывает на то, что Chrome включил сборщик мусора, чтобы освободить память. Версия Redux полностью соответствует своему низкому профилю памяти на протяжении всего прогона.

Давайте внимательно посмотрим на профили временной шкалы для всех трех версий.

Выполнение AngularJS вызвало несколько пауз сборщика мусора по мере обновления списка тикеров. V8 пытается скрыть паузы сборщика мусора, планируя их во время неиспользуемых периодов простоя, чтобы улучшить отзывчивость пользовательского интерфейса. В отличие от этого идеального поведения, паузы сборки мусора происходили во время выполнения скрипта, что увеличивало общее время выполнения.

Профиль производительности Redux не показывает никаких пауз сборщика мусора во время выполнения скрипта.

Профиль Mobx показывает несколько пауз сборщика мусора, но не так много, как версия AngularJS.

Сценарий 2 - Добавление тикеров

Мы будем добавлять 50 тикеров в видимую коллекцию каждые 100 мс, пока не отобразим все тикеры. Результат отображения всех 5000 тикеров - нереальный сценарий, но было бы интересно посмотреть, как каждый фреймворк справляется с этим.

На приведенной ниже диаграмме показано время выполнения скрипта на временной шкале производительности Chrome. В случае AngularJS время выполнения скрипта линейно увеличивалось по мере того, как на страницу добавлялось все больше и больше тикеров. AngularJS потребовалось больше времени для добавления новых тикеров с самого начала по сравнению с другими версиями.

Интересно, что версии Redux и Mobx показывают впечатляющую производительность даже в правой части диаграммы с тысячами тикеров на странице. Алгоритм сравнения виртуальных DOM в React показывает свою мощь по сравнению с грязной проверкой AngularJS.

В AngularJS добавление новых элементов вызывало дряблость в браузере с самого начала (красные полосы), а количество кадров в секунду сбрасывалось с 60 на ранней стадии и никогда не восстанавливалось (зеленая область) в течение всей операции добавления.

Redux однажды создал джанк на ранней стадии, но все ясно, пока мы не перешли половину пути добавления новых тикеров. FPS также неплохо восстановился до 60 между операциями добавления.

Mobx вызывал jank в несколько раз чаще, чем Redux, но нигде и близко к AngularJS.

События памяти и GC

Redux потреблял примерно половину размера кучи, как AngularJS, в течение всего запуска. Мобкс остался посередине.

Добавление новых тикеров также вызывало некоторые паузы сборщика мусора с помощью AngularJS (почти один раз при каждой операции добавления). В целом Redux вызывал меньше пауз сборщика мусора. Mobx начал вызывать больше пауз сборщика мусора ближе к концу, поскольку мы добавляли в список все больше и больше тикеров.

Сценарий 3 - Быстрое изменение цены / объема

Это наиболее распространенный сценарий в приложениях реального времени. После рендеринга представления в приложение будет быстро поступать ряд обновлений через веб-сокеты, вызовы xhr и т. Д. Представьте себе варианты использования, такие как обновление информации о присутствии, изменение цен на акции, изменение количества лайков / ретвитов / аплодисментов и многое другое. Давайте посмотрим, как работает каждая платформа в этом сценарии.

Все нижеприведенные показатели берутся с 1500 тикерами на странице и когда изменения цены / объема происходят каждые 10 мс.

AngularJS снова изо всех сил пытался успевать за обновлениями, происходящими в быстрой последовательности. Выполнение скрипта для каждого обновления занимало около 35 мсек. Redux обновил представление за 6 мс. Mobx светится, обновляя вид в течение 2 мс. Граф вывода Mobx точно знает, какой компонент обновлять, в зависимости от того, какое состояние наблюдаемого объекта изменилось.

Вот профили временной шкалы, показывающие выполнение скрипта для каждой версии.

FPS стабильно оставался на уровне 60 с Redux и Mobx, тогда как с AngularJS он колебался чуть ниже 30.

Сценарий 4 - Удаление тикеров

Мы добавим все 5000 тикеров на страницу и будем удалять 50 тикеров из видимой коллекции каждые 100 мс.

На изображениях ниже показан профиль производительности начальных итераций удаления. AngularJS почти в 4 раза медленнее по сравнению с версиями React. Redux и Mobx заняли немного больше времени на начальных итерациях, но установили 50–70 мс для каждой операции удаления.

Из всех вышеперечисленных тестов довольно ясно, что React дает значительный прирост производительности по сравнению с AngularJS.

По мере роста приложений и усложнения представлений профиль времени выполнения фреймворков начинает различаться по-разному. Наша цель состояла в том, чтобы воспроизвести сценарии, на которые мы нацелились, измерить влияние на производительность / память и посмотреть на плюсы и минусы конструкций с каждой структурой.

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

Я рассмотрю основные концепции, преимущества и недостатки Redux и Mobx в отдельном посте.

Спасибо за прочтение. Надеюсь, это будет полезно.

P.S. Благодарим Шьяма Арджарапу и Адама Карра за рецензирование этой статьи.