Оптимизация производительности¶
React использует несколько умных подходов для минимизации количества дорогостоящих DOM-операций, необходимых для обновления пользовательского интерфейса. Для многих приложений, использование React приведёт к быстрому пользовательскому интерфейсу без особых усилий по оптимизации производительности. Тем не менее, существует несколько способов ускорить React-приложение.
Использование продакшен-сборки¶
Если вы испытываете проблемы с производительностью в React-приложении, убедитесь в том, что вы проводите тесты с настройками минифицированной продакшен-сборки.
По умолчанию в React есть много вспомогательных предупреждений, очень полезных при разработке. Тем не менее, они делают React больше и медленнее, поэтому вам обязательно следует использовать продакшен-версию при деплое приложения.
Если вы не уверены в том, что процесс сборки настроен правильно, вы можете проверить это, установив React Developer Tools for Chrome. Если вы посетите сайт, работающий на React в продакшен-режиме, иконка будет с чёрным фоном:
Если вы посетите сайт с React в режиме разработки, у иконки будет красный фон:
Как правило, режим разработки используется во время работы над приложением, а продакшен-режим при деплое приложения для пользователей.
Ниже вы можете найти инструкцию по сборке своего приложения для продакшена.
Create React App¶
Если ваш проект сделан с помощью Create React App, выполните:
1 |
|
Эта команда создаст продакшен-сборку вашего приложения в папке build/
вашего проекта.
Помните, что это необходимо только перед деплоем на продакшен. Для обычной разработки используйте npm start
.
Однофайловые сборки¶
Мы предлагаем готовые для продакшена версии React и React DOM в виде отдельных файлов:
1 2 |
|
Помните, что для продакшена подходят только те файлы, которые заканчиваются на .production.min.js
.
Brunch¶
Для наиболее эффективной продакшен-сборки с Brunch, установите плагин uglify-js-brunch
.
1 2 3 4 5 |
|
Затем, для создания продакшен сборки, добавьте флаг -p
к команде build
:
1 |
|
Помните, что это нужно делать только для продакшен-сборки. Вам не нужно использовать флаг -p
или применять этот плагин во время разработки, потому что это скроет вспомогательные предупреждения React и замедлит процесс сборки.
Browserify¶
Для наиболее эффективной продакшен-сборки с Browserify, установите несколько плагинов:
1 2 3 4 5 |
|
При создании продакшен-сборки, убедитесь, что вы добавили эти пакеты для преобразования (порядок имеет значение):
- Плагин
envify
обеспечивает правильную среду для сборки. Сделайте его глобальным (-g
). - Плагин
uglifyify
удаляет импорты, добавленные при разработке. Сделайте его глобальным (-g
). - Наконец, полученная сборка отправляется к
uglify-js
для минификации (прочитайте, зачем это нужно).
К примеру:
1 2 3 4 |
|
Примечание:
Имя пакета
uglify-js
, но фактически он предоставляет исполняемый файл с именемuglifyjs
.
Это не опечатка.
Помните, что это нужно делать только для продакшен-сборки. Вам не следует использовать эти плагины в процессе разработки, потому что это скроет вспомогательные предупреждения React и замедлит процесс сборки.
Rollup¶
Для наиболее эффективной продакшен-сборки с Rollup, установите несколько плагинов:
1 2 3 4 5 |
|
При создании продакшен-сборки, убедитесь, что вы добавили эти плагины (порядок имеет значение):
- Плагин
replace
обеспечивает правильную среду для сборки. - Плагин
commonjs
обеспечивает поддержку CommonJS в Rollup. - Плагин
uglify
сжимает и оптимизирует финальную сборку.
1 2 3 4 5 6 7 8 9 |
|
Полный пример настройки можно посмотреть здесь.
Помните, что это нужно делать только для продакшен-сборки. Вам не следует использовать плагин uglify
или плагин replace
со значением 'production'
в процессе разработки, потому что это скроет вспомогательные предупреждения React и замедлит процесс сборки.
webpack¶
Примечание:
Если вы используете Create React App, пожалуйста, следуйте инструкциям выше.
Этот раздел подойдёт для тех, кто самостоятельно настраивает webpack.
Для наиболее эффективной продакшен-сборки с помощью webpack обязательно включите эти плагины в конфигурацию:
1 2 3 4 |
|
Вы можете узнать об этом больше в документации webpack.
Помните, что это нужно делать только для продакшен-сборки. Вам не стоит использовать UglifyJsPlugin
или DefinePlugin
со значением 'production'
в процессе разработки, потому что тогда скроются вспомогательные предупреждения React и замедлится процесс сборки.
Анализ производительности компонентов с помощью вкладки Chrome «Performance»¶
В режиме разработки вы можете видеть как компоненты монтируются, обновляются и размонтируются с помощью инструментов производительности в браузерах, которые их поддерживают. Например:
Для того, чтобы сделать это в Chrome:
-
Временно отключите все расширения Chrome, особенно React DevTools. Они могут существенно исказить результаты!
-
Убедитесь, что вы запускаете приложение в режиме разработки.
-
Откройте в инструментах разработчика Chrome вкладку Performance и нажмите Record.
-
Выполните действия, которые вы хотите проанализировать на производительность. Не записывайте более 20 секунд, иначе Chrome может зависнуть.
-
Остановите запись.
-
События React будут сгруппированы под меткой User Timing.
Для более детального ознакомления, посмотрите эту статью от Бена Шварца (Ben Schwarz).
Обратите внимание, что результаты являются относительными и в продакшене рендеринг компонентов будет быстрее. Всё же это должно помочь вам понять, когда не имеющий отношения пользовательский компонент обновляется по ошибке, а также как глубоко и часто обновляется пользовательский интерфейс.
В настоящее время Chrome, Edge и IE единственные браузеры, которые поддерживают данную возможность, но мы используем стандарт User Timing API поэтому ожидайте, что больше браузеров добавят его поддержку.
Анализ производительности компонентов с помощью инструмента разработки «Profiler»¶
Пакеты react-dom
версии 16.5+ и react-native
версии 0.57+ предоставляют расширенные возможности анализа производительности в режиме разработки с помощью инструментов разработчика React Profiler. Обзор профайлера можно найти в посте блога "Введение в React Profiler". Пошаговое видео-руководство также доступно на YouTube.
Если вы ещё не установили инструменты разработчика React, вы можете найти их здесь:
Примечание
Профилирование продакшен-пакета для
react-dom
также доступно какreact-dom/profiling
. Подробнее о том, как использовать этот пакет, читайте на fb.me/react-profiling
Виртуализация длинных списков¶
Если ваше приложение рендерит длинные списки данных (сотни или тысячи строк), мы рекомендуем использовать метод известный как "оконный доступ". Этот метод рендерит только небольшое подмножество строк в данный момент времени и может значительно сократить время, необходимое для повторного рендера компонентов, а также количество создаваемых DOM-узлов.
react-window и react-virtualized — это популярные библиотеки для оконного доступа. Они предоставляют несколько повторно используемых компонентов для отображения списков, сеток и табличных данных. Если вы хотите использовать что-то более специфическое для вашего конкретного случая, то вы можете создать собственный компонент с оконным доступом, как это сделано в Twitter.
Избежание согласования¶
React создаёт и поддерживает внутреннее представление отображаемого пользовательского интерфейса. Оно также включает React-элементы возвращаемые из ваших компонентов. Это представление позволяет React избегать создания DOM-узлов и не обращаться к текущим без необходимости, поскольку эти операции могут быть медленнее, чем операции с JavaScript-объектами. Иногда его называют "виртуальный DOM", но в React Native это работает точно так же.
Когда изменяются пропсы или состояние компонента, React решает нужно ли обновление DOM, сравнивая возвращённый элемент с ранее отрендеренным. Если они не равны, React обновит DOM.
Вы можете визуализировать эти перерисовки виртуального DOM с помощью инструментов разработчика React:
В консоли разработчика выберите параметр Highlight Updates на вкладке React:
Взаимодействуя со своей страницей, вы должны увидеть, что вокруг любых компонентов, которые были повторно отрендерены появляются цветные границы. Это позволит вам выявлять лишний повторный рендеринг. Вы можете узнать больше о возможностях инструментов разработки React из этого поста в блоге Бена Эдельштейна (Ben Edelstein).
Рассмотрим такой пример:
Обратите внимание, что когда мы вводим вторую задачу, первая также мигает на экране при каждом нажатии клавиши. Это означает, что она ререндерится вместе с полем ввода. Иногда это называют "бесполезным" рендерингом. Мы знаем, что в этом нет необходимости, так как содержимое первой задачи не изменилось, но React этого не знает.
Несмотря на то, что React обновляет только изменённые DOM-узлы, повторный рендеринг всё же занимает некоторое время. В большинстве случаев это не проблема, но если замедление заметно, то вы можете всё ускорить, переопределив метод жизненного цикла shouldComponentUpdate
, который вызывается перед началом процесса ререндеринга. Реализация этой функции по умолчанию возвращает true
, указывая React выполнить обновление:
1 2 3 |
|
Если вы знаете ситуации, в которых ваш компонент не нуждается в обновлении, вы можете вернуть false
из shouldComponentUpdate
, чтобы пропустить весь процесс рендеринга, включая вызов render()
и так далее ниже по иерархии.
В большинстве случаев вместо того, чтобы писать shouldComponentUpdate()
вручную, вы можете наследоваться от React.PureComponent
. Это эквивалентно реализации shouldComponentUpdate()
с поверхностным сравнением текущих и предыдущих пропсов и состояния.
shouldComponentUpdate в действии¶
Вот поддерево компонентов. Для каждого из них SCU
указывает что возвратил shouldComponentUpdate
, а vDOMEq
указывает эквивалентны ли отрендеренные React элементы. Наконец, цвет круга указывает требуется ли согласовать компонент или нет.
Поскольку shouldComponentUpdate
возвратил false
для поддерева с корнем C2, React не пытался отрендерить C2, следовательно не нужно вызывать shouldComponentUpdate
на C4 и C5.
Для C1 и C3 shouldComponentUpdate
возвратил true
, поэтому React пришлось спуститься к листьям и проверить их. Для C6 shouldComponentUpdate
вернул true
, и поскольку отображаемые элементы не были эквивалентны, React должен был обновить DOM.
Последний интересный случай — C8. React должен был отрисовать этот компонент, но поскольку возвращаемые им React-элементы были равны ранее предоставленным, ему не нужно обновлять DOM.
Обратите внимание, что React должен был делать изменения только для C6. Для C8 этого удалось избежать сравнением отрендеренных React-элементов, а для поддеревьев C2 и C7 даже не пришлось сравнивать элементы, так как нас выручил shouldComponentUpdate
и render
не был вызван.
Примеры¶
Если единственный случай изменения вашего компонента это когда переменная props.color
или state.count
изменяются, вы могли бы выполнить проверку в shouldComponentUpdate
следующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
В этом коде shouldComponentUpdate
— это простая проверка на наличие каких-либо изменений в props.color
или state.count
. Если эти значения не изменяются, то компонент не обновляется. Если ваш компонент стал более сложным, вы можете использовать аналогичный паттерн "поверхностного сравнения" между всеми полями props
и state
, чтобы определить должен ли обновиться компонент. Этот механизм достаточно распространён, поэтому React предоставляет вспомогательную функцию для работы с ним — просто наследуйтесь от React.PureComponent
. Поэтому, следующий код — это более простой способ добиться того же самого эффекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
В большинстве случаев вы можете использовать React.PureComponent
вместо написания собственного shouldComponentUpdate
. Но он делает только поверхностное сравнение, поэтому его нельзя использовать, если пропсы и состояние могут измениться таким образом, который не сможет быть обнаружен при поверхностном сравнении.
Это может стать проблемой для более сложных структур данных. Например, вы хотите, чтобы компонент ListOfWords
отображал список слов, разделённых через запятую, с родительским компонентом WordAdder
, который позволяет кликнуть на кнопку, чтобы добавить слово в список. Этот код работает неправильно:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
Проблема в том, что PureComponent
сделает сравнение по ссылке между старыми и новыми значениями this.props.words
. Поскольку этот код мутирует массив words
в методе handleClick
компонента WordAdder
, старые и новые значения this.props.words
при сравнении по ссылке будут равны, даже если слова в массиве изменились. ListOfWords
не будет обновляться, даже если он содержит новые слова, которые должны быть отрендерены.
Сила иммутабельных данных¶
Лучший способ решения этой проблемы — избегать мутирования значений, которые вы используете как свойства или состояние. К примеру, описанный выше метод handleClick
можно переписать с помощью concat
следующим образом:
1 2 3 4 5 |
|
ES6 поддерживает синтаксис расширения для массивов, который поможет сделать это проще. Если вы используете Create React App, то этот синтаксис доступен там по умолчанию.
1 2 3 4 5 |
|
Таким же образом вы можете переписать код, который мутирует объекты. К примеру, мы имеем объект с именем colormap
и хотим написать функцию, которая изменяет colormap.right
на 'blue'
. Мы могли бы написать:
1 2 3 |
|
Чтобы написать это без мутирования исходного объекта, мы можем использовать метод Object.assign:
1 2 3 |
|
Функция updateColorMap
теперь возвращает новый объект, вместо того, чтобы мутировать исходный. Метод Object.assign
входит в ES6 и требует полифила.
JavaScript рассматривает предложение добавить синтаксис расширения свойств объекта для добавления свойств объекта, чтобы упростить его обновление без мутаций:
1 2 3 |
|
Если вы используете Create React App, то Object.assign
и синтаксис расширения объектов доступны вам по умолчанию.
Использование неизменяемых структур данных¶
Библиотека Immutable.js — ещё один способ решить эту проблему. Эта библиотека предоставляет иммутабельные, персистентные коллекции, которые работают с помощью механизма "structural sharing":
- Иммутабельность: после создания коллекции она не может быть изменена.
- Персистентность: новые коллекции могут быть созданы из предыдущей коллекции и мутированы c помощью set. После создания новой коллекции исходная коллекция останется по-прежнему неизменной.
- Structural Sharing: новые коллекции создаются с использованием такой же структуры как у исходной коллекции, что позволяет сократить количество копий до минимума для повышения производительности.
Иммутабельность делает отслеживание изменений дешёвым. Изменение всегда приведёт к созданию нового объекта, поэтому нам нужно только проверить, изменилась ли ссылка на объект. Например, в этом обычном JavaScript коде:
1 2 3 4 |
|
Несмотря на то, что y
был изменён, поскольку это ссылка на тот же объект, что и x
, сравнение вернёт true
. Вы можете написать аналогичный код с помощью immutable.js:
1 2 3 4 5 6 |
|
В этом случае, поскольку после мутирования x
возвращается новая ссылка, мы можем использовать строгое сравнение (в данном случае по ссылке) (x === y)
для того, чтобы убедиться, что новое значение хранящееся в y
отличается от исходного значения, хранящегося в x
.
Есть другие библиотеки, которые могут помочь вам использовать иммутабельные данные: Immer, immutability-helper и seamless-immutable.
Иммутабельные структуры данных предоставляют вам дешёвый способ отслеживания изменений в объектах и всё, что вам нужно для реализации shouldComponentUpdate
. В большинстве случаев это даст вам хороший прирост в производительности.