React / Redux - прекрасное сочетание. Тем не менее, каждое принятое вами решение может существенно повлиять на производительность. Оба предлагают улучшения производительности прямо из коробки. React оптимизирует обновления DOM. Redux оптимизирует обновления вашего дерева состояний. Учитывая эти оптимизации, разработчики предполагают, что производительность - это само собой разумеющееся. Что ж, это не так.

Эта статья предполагает следующие знания:

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

Что меня больше всего интересует, так это то, что когда я «оптимизирую производительность» в React / Redux, какая разница в каждой оптимизации?

Я мог использовать whyDidYouUpdate, чтобы убедиться, что я удалил ненужные обновления. Я мог использовать React dev tools, чтобы наблюдать за обновлениями. Я мог бы даже использовать Таблицы пламени Chrome для визуальной обратной связи. В конце концов, мне нужно было знать, насколько мои усилия повлияли на производительность. Улучшения в 2 раза, улучшения в 3 раза?

Вот почему я обратился за помощью к benchmark.js. Более того, есть адаптер Karma для benchmark.js, который выводит результаты в командную строку. Я могу запускать их без головы, без необходимости обновлять браузер.

Еще одно преимущество тестов по сравнению с инструментами профилирования заключается в том, что мы можем видеть, как вещи работают в меньшем масштабе. Трудно заметить улучшения, когда они представляют собой крошечный отрезок на временной шкале. Числа рассказывают гораздо более прямую историю.

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

Установка

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

Вы можете поиграть с исходным кодом здесь:
https://github.com/sarmstrong/react-redux-benchmarks

«Упражнение» - это базовый список со списками дочерних элементов. Я выбрал эту структуру, потому что она создает дерево. Данные представляют собой дерево, а представления имеют древовидную структуру.

<ul>
    <li>Item One
        <ul>
            <li>Item One - Sub Item One</li>
            <li>Item One - Sub Item Two</li>
            <li>Item One - Sub Item Three</li>
        </ul>
    </li>
    <!-- etc . . . . -->
</ul>

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

Redux еще больше дополняет эту идею. Он создает дерево состояний, которое мы используем для наполнения наших иерархических структур представления.

Имея в виду производительность, я хотел понять влияние React / Redux на наши данные. Итак, я проверил следующее:

  1. Насколько наше представление React может повлиять на наше приложение
  2. Насколько наша структура данных Redux может повлиять на наше приложение

Насколько наше представление React может повлиять на наше приложение?

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

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

  1. Функциональный синтаксис

2. Стандартный компонент React

3. React Pure Component.

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

Насколько наши данные Redux могут повлиять на наше приложение?

Затем я решил посмотреть, какая разница в форме данных в приложении. Это одна из первых рекомендаций Redux docs при внедрении редукторов.

В этой серии экспериментов я «сплющил» структуру объекта. Потомки больше не вложены в родительские объекты. Я определил родительские элементы верхнего уровня как простую коллекцию идентификаторов.

Затем я хотел проверить несколько условий:

  1. Что произошло, когда данные не изменились
  2. Что происходит при изменении данных

Я выбрал эту настройку, чтобы проиллюстрировать точку, чтобы показать, как наше приложение Redux / React реагирует на изменения.

Барабанная дробь, пожалуйста. . . . .

Так каковы результаты, спросите вы? Вот распечатка тестов на момент написания этой статьи. (Используя React 15)

При использовании неоптимальной структуры данных чистые компоненты превосходят функциональные и стандартные компоненты. Однако, когда мы оптимизируем структуру данных, структура представления не имеет значения.

Без обновлений оптимизированная структура может привести к резкому увеличению производительности. Даже с обновлениями все равно наблюдается значительный прирост производительности. Этого достаточно, чтобы оправдать любое улучшение производительности. (Кто бы не хотел, чтобы их приложение работало в 2–3 раза быстрее?)

Итак, какой во всем этом смысл ????

Когда мы передаем свойства через родительский компонент потомкам, нам нужно изменить объект верхнего уровня. Это означает, что каждый компонент может обновляться.

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

Когда мы переключаемся на использование чистых компонентов, мы получаем некоторый контроль над обновлениями. Нам все еще нужно изменить состояние верхнего уровня, но ссылки на второй и третий элементы списка не изменяются. Shallow equals предотвращает рендеринг компонента и его соответствующих дочерних компонентов. Вот почему мы видим прирост производительности.

Когда мы оптимизируем структуру и не меняем контент, React никогда не обновляется. Вот здесь-то и вступает в действие Redux. Подключенные компоненты по умолчанию чистые. Если свойства совпадают, обновление никогда не достигает подключенного компонента.

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

Используя эту информацию, я могу понять, что форма данных оказывает наибольшее влияние на производительность React / Redux. Это кажется нелогичным, но для повышения производительности лучше всего использовать «больше» подключенных компонентов. Конечно, сначала вам нужно оптимизировать структуру данных.

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

Итак, вот и все, как использовать тесты производительности, чтобы рассказать историю производительности. Это не новая идея, но это также то, к чему мы, естественно, не стремимся на Front End. Мы тратим много времени на анализ загрузки страницы или TTI, но я рискну, что большинство из нас не тратит много времени на тестирование исходного кода.

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

Еще одно преимущество тестов, которое здесь не упоминается, - это возможность увидеть влияние апстрима. Обновление контрольного показателя подкомпонента может повлиять на родительский компонент. Если нет никакого влияния, это может побудить вас вместо этого оценить другие компоненты.

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