TL; DR;

Основная проблема производительности в приложениях React - это избыточная обработка и различия в компонентах DOM. Во избежание этого возврата false из shouldComponentUpdate как можно выше в вашем приложении.

Чтобы облегчить это:

  1. Сделайте проверку shouldComponentUpdate быстрой
  2. Сделайте проверку shouldComponentUpdate простой

видео

Вы можете посмотреть видеозапись моего разговора на эту тему на YouTube.

Отказ от ответственности!

Примеры в этом блоге будут использовать React + Redux. Если вы используете другую библиотеку потока данных, принципы будут применяться, но реализация будет другой.

В этом блоге я не использовал библиотеку неизменяемости, а использовал только vanilla es6 и немного es7. Некоторые вещи становятся проще при использовании библиотеки неизменяемости, но я не буду их здесь обсуждать.

Какая основная точка производительности в приложениях React?

  1. Избыточная обработка в компонентах, которые не обновляют DOM
  2. Дифференциальные листовые узлы DOM, которые не нуждаются в обновлении
    - хотя DOM-дифференциация невероятна и облегчает React, это нетривиально в вычислительном отношении

Каково поведение рендеринга по умолчанию в React?

Давайте посмотрим, как React отображает компоненты.

Начальный рендер

При первоначальном рендеринге нам нужно, чтобы все приложение рендерило
(зеленый = узлы, которые рендерились)

Отрисован каждый узел - это хорошо! Наше приложение теперь представляет наше начальное состояние

Предлагаемое изменение

Мы хотим обновить часть данных. Это изменение актуально только для одного листового узла.

Идеальное обновление

Мы хотим визуализировать только те узлы, которые находятся на критическом пути к нашему листовому узлу.

Поведение по умолчанию

Это то, что делает React, если вы не говорите иначе
(оранжевый = отходы)

О, нет! Все наши узлы отрисованы.

Каждый компонент в React имеет функцию shouldComponentUpdate (nextProps, nextState). Эта функция отвечает за возврат true, если компонент должен обновляться, и false, если компонент не должен обновляться. Возврат false приводит к тому, что функция компонентов render вообще не вызывается. Поведение по умолчанию в React таково, что shouldComponentUpdate всегда возвращает true, даже если вы не определяете функцию shouldComponentUpdate явно.

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

Как получить идеальное обновление?

Верните false из shouldComponentUpdate как можно выше в приложении.

Чтобы облегчить это:

  1. Сделайте проверку shouldComponentUpdate быстрой
  2. Сделайте проверку shouldComponentUpdate простой

Быстрая проверка shouldComponentUpdate

В идеале мы не хотим проводить глубокую проверку равенства в наших функциях shouldComponentUpdate, поскольку они дороги, особенно при масштабировании и с большими структурами данных.

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

Используя эту технику в редукторе Redux:

Если вы примете этот подход, то все, что вам нужно сделать в функции shouldComponentUpdate, - это проверить ссылки.

Пример реализации isObjectEqual

Упростите проверку shouldComponentUpdate

Пример жесткого shouldComponentUpdate

Такое структурирование данных затрудняет выполнение проверок в shouldComponentUpdate .

Проблема 1. Огромные функции shouldComponentUpdate

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

  1. возвращает false, когда вы не должны (состояние отображается в приложении неправильно)
  2. возвращает true, когда не следует (проблема с производительностью)

Зачем усложнять себе жизнь? Вы хотите, чтобы эти проверки были настолько простыми, что вам не нужно было о них серьезно думать.

Проблема 2: тесная связь родителей с детьми

Обычно приложения хотят способствовать слабой связи (компоненты знают о других компонентах как можно меньше). Родительские компоненты должны как можно меньше понимать, как работают их дочерние компоненты. Это позволяет вам изменять поведение детей, при этом родитель не должен знать об изменении (при условии, что PropTypes останутся прежними). Это также позволяет детям действовать изолированно, не требуя от родителей жесткого контроля над их поведением.

Исправление: денормализовать данные

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

Подобная структура данных упрощает выполнение проверок в shouldComponentUpdate легко.

Если вы хотите обновить взаимодействие, вы измените ссылку на весь объект.

Попался: проверка ссылок и динамический реквизит

Пример создания динамического реквизита

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

Обычно это используется при создании функций.

Стратегии решения проблемы

1. Избегайте создания динамических свойств внутри компонентов.

улучшите свою модель данных, чтобы вы могли просто передавать реквизиты прямо через

2. Передайте динамические свойства как типы, удовлетворяющие === равенству.

например:
- логическое
- число
- строка

Если вам действительно нужно передать динамический объект, вы можете передать строковое представление объекта, который может быть деконструирован в дочернем элементе

Частный случай: функции

  1. Не передавайте функции, если можете этого избежать. Скорее позвольте дочернему элементу отправлять действия, когда он захочет. Это дает дополнительное преимущество, заключающееся в перемещении бизнес-логики из компонентов.
  2. Игнорируйте функции в проверке shouldComponentUpdate. Это не идеально, поскольку невозможно узнать, изменилось ли значение функции.
  3. Создайте карту данных - ›функция, которая не меняется. Вы можете поместить их в состояние в своей функции componentWillReceiveProps. Таким образом, каждый рендер не получит новую ссылку. Этот метод очень тяжелый, так как вам нужно поддерживать и обновлять список функций.
  4. Создайте средний компонент с правильной привязкой. Это также не идеально, поскольку вы вводите избыточный уровень в свою иерархию.
  5. Все, что вы можете придумать, позволяет избежать создания новой функции при каждом вызове render!

Пример стратегии №4

Инструменты

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

console.time

Это довольно просто:

  1. запустить таймер
  2. делать вещи
  3. остановить таймер

Отличный способ сделать это - использовать промежуточное ПО Redux:

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

React.perf

В этом используется та же идея, что и в console.time, но с использованием служебных инструментов для повышения производительности React:

  1. Perf.start ()
  2. делать вещи
  3. Perf.stop

Пример промежуточного программного обеспечения Redux:

Подобно методу console.time, это позволит вам увидеть показатели производительности для каждого из ваших действий. Для получения дополнительной информации об аддоне производительности React см. Здесь

Инструменты браузера

Таблицы пламени профилировщика ЦП также могут быть полезны при поиске проблем с производительностью в ваших приложениях.

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

Firefox: см. Здесь

Chrome: см. Здесь

Спасибо за чтение и всего наилучшего в создании высокопроизводительных приложений на React!

Обновлять

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