Как оптимизировать react приложение
React — хорошо оптимизированная библиотека, “из коробки” гарантирующая высокую производительность. Но любой оптимизации может настать конец, если не правильно подойти к разработке.
Эта статья рассчитана на тех, кто уже начал работать с React, но еще никогда не сталкивался с серьезными проблемами производительности. В ней я расскажу ни столько о способах отладки производительности, а, скорей, о мерах предотвращения таких проблем.
React использует, так называемый, Virtual DOM, основная идея которого — сократить количество обращений к DOM API. Операции с DOM более затратные, чем работа с обычными объектами в Javascript, поэтому React перерисовывает DOM только в том случае, если состояние компонента изменилось.
R e act имеет иерархическую структуру компонентов. Когда производится обновление старшего компонента, цепочка обновлений начинается распространяться от старших компонентов к потомкам, даже если props этих компонентов не менялись.
Лишь при достижении компонентов, непосредственно связанных с DOM сущностями, производится применение входящих props на Virtual DOM, оценка различий и перерисовка реального DOM.
Сокращайте количество перерисовки DOM
DOM элемент будет перерисован, если хотя бы один ключевой prop, который пришел в его React контроллер, отличается от предыдущего.
Для сравнения старого и нового значения prop используется строгое сравнение, которое, упрощенно говоря, проверяет ссылается ли первая и вторая переменная на одну и ту же область памяти. Понимание этого признака равенства поможет вам лишний раз не обновлять DOM.
Так, например, два объекта, одинаковых по содержанию, но созданных двумя разными вызовами конструктора — не равны. Это тождество справедливо и для функций, массивов и символа.
То есть, когда мы говорим об объектах и функциях в Javascript, мы должны помнить, что любой новый объект, функция, массив — это всегда новая область памяти.
А вот две строки, одинаковых по содержанию, но созданные дважды, будут равны, поскольку Javascript оптимизирует хранение в памяти одинаковых строк.
То же самое касается числовых и булевых значений, null и undefined.
Я рассказываю про сравнение различных типов данных, потому что распространенной ошибкой является передача объектов и функций в props компонента, только что созданных в том же стеке, что и сам вызов.
В примере выше, каждый вызов MyButtonComponent будет передавать в компонент button новое значение для prop onClick, что в свою очередь будет каждый раз провоцировать обращение к DOM API.
Такой проход не является фатальной ошибкой, и даже допускается в небольших приложениях, но в крупных приложениях или в особых случаях может привести к падению производительности. Поэтому привычку создавать функции и объекты во время рендеринга лучше вообще не заводить.
Передавая в props объект или массив, созданный непосредственно в теле функции, даже если его свойства остаются теми же — это тоже антипаттерн, приводящий к таким же проблемам. Из за того, что конструкторы <> и [] всякий раз создают новый экземпляр объекта, React компонент будет считать, что данные изменились и нужно перерисовывать элемент.
Вместо этого заранее подготавливайте функции-обработчики и сохраняйте их в свойства классов, используйте мемоизацию с хуками (например useCallback) или с компонентами высшего порядка. А объекты кешируйте с помощью useState, useMemo, сохраняйте в свойствах классов или вообще — в корне модуля.
Пример правильного обработчика для onClick в данном случае (с использованием хука):
Хук useCallback гарантирует, что переменная clickHandler всегда будет ссылаться на одну и ту же область памяти.
Сокращайте количество обновлений компонентов
Помимо перерисовки DOM, сами компоненты так же могут содержать тяжелую логику, которая будет выполняется при обновлении. Поэтому логично не обновлять такие компоненты, props для которой не изменились.
По-умолчанию React не предпринимает никаких действий для проверки изменились ли props в пользовательских компонентах, но предлагает коробочный способ для этой проверки: класс React.PureComponent для компонентов-классов и React.memo — для функциональных компонентов.
Но не стоит применять чистые компоненты повсеместно. Нужно понимать, что на сравнение props тоже нужны временные ресурсы и если сделать все компоненты в приложении на базе PureComponent, то это может дать обратный эффект.
Альтернативным способом является использование метода жизненного цикла React shouldComponentUpdate, который позволяет точечно сравнить старые и новые props и на основе этого принять решение о необходимость дальнейшего обновления компонента.
Но я обычно не использую ни PureComponent, ни React.memo, ни shouldComponentUpdate. Вместо этого я вручную сравниваю предыдущие props и новые props в тех частях компонента, где присутствует тяжелая логика, не прерывая жизненный цикл в целом. Это можно делать как в классах, так и с помощью ХОКов, так и с помощью хуков.
Если вы уверены, что сравнение props будет стоит дешевле, чем холостой прогон всех дочерних компонентов, то используйте вышеуказанные способы. Всегда старайтесь находить компромисс между разными подходами, оценивая их стоимость.
Применяйте виртуализацию для больших списков
Виртуализация — очень старая технология, она была и будет оставаться актуальной всегда, потому что помогает создать иллюзию присутствия на странице очень большого (бесконечного) количества элементов, а фактически содержит только то количество, которое необходимо для их отображения в видимой области.
Когда вы просматриваете веб-сайты, мобильные приложения и, к примеру, листаете ленту новостей или список аудиотреков, возможно, даже не догадываетесь, что вы имеете дело с виртуализацией.
Когда нужна виртуализация? Когда вам нужно отображать массивные списки. К примеру, отобразить на странице миллион элементов. Не трудно догадаться, что попытка рендера миллиона элементов приведет к серьезным проблемам с производительностью:
Рендеринг этого кода у меня занял полминуты
Что бы избежать проблем с производительностью при отрисовки миллиона элементов, отображение элементов или необходимо разбить на страницы (применить классический паттерн пейджинации), или использовать виртуализацию.
Механика виртуализации слишком сложна, что бы привести краткий пример ее устройства. В частом случае, виртуализация учитывает количество видимых элементов на странице, и количество элементов, которые могут стать видимыми в ближайшее время. Как только пользователь совершил скролл (или свайп) страницы, элементы которые ушли из зоны видимости (вверх например) сразу же удаляются, а элементы которые должны прийти в зону видимости — создаются.
Более оптимизированным вариантом витуализации является подход, при котором элементы, покинувшие зону видимости, без удаления сразу же используются в качестве приходящих элементов в зону видимости с другой стороны. Таким образом не нужно удалять и создавать DOM узлы, одни и те же DOM узлы просто перемещаются туда сюда.
При виртуализации, несмотря на видимую безграничность скролла, одновременное количество элементов на странице всегда одинаково небольшое.
Этот подход очень похож на технологию в компьютерных играх, называемую Frustum Culling, которая позволяет не отрисовывать те объекты, которые находятся за пределами области видимости игрока. Концепция идентичная — если мы что-то не видим, мы на это не рисуем.
React “под капотом” может автоматически диактивировать компоненты, которые находятся за пределами видимости. Но увы этого функционала не хватает для оптимизации рендеринга массивных списков.
Примером виртуализации из реального мира так же может служить массовка в театре и кино: когда дюжина человек путем быстрого переодевания за кадром и хождением туда сюда создает эффект бесконечного потока пешеходов.
Скорей всего вы будете использовать готовые решения для виртуализации, такие как react-virtualized. Самостоятельная реализация виртуализации не так проста и за нее стоит браться только в особых случаях, когда нету готового решения.
Ищите и лечите тяжелые функции
С тех пор как я (3 года назад) перешел на React (до этого работая на Angular, а еще раньше с jQuery) я почти не встречал серьезных проблем со скоростью рендеринга страниц моих приложений, которые бы потребовали анализа производительности. Если соблюдать основные нормативы грамотности писания кода под React, никаких проблем быть не должно. React — очень хорошо оптимизированный движок.
Но если количество логики и количество повторного использования этой логики в приложении возрастает, уязвимыми для производительности местами в приложении становятся отдельные функции, время на выполнение которых, может быть незначительным при единичном вызове, но заметно снижает производительность при многократном.
К примеру, если какая нибудь вспомогательная функция deepCompare в среднем выполняется за 5ms (что в единичном случае, казалось бы, не так значительно), но эта функция вызывается при рендеринге 200 компонентов на странице, то нужно понимать, что только первичный рендеринг такой страницы увеличится на секунду, что уже недопустимо.
Что бы сократить время рендеринга, в данном случае, вам нужно:
- Найти эту некую тяжелую функцию;
- Попытаться оптимизировать эту функцию или заменить ее на что-то другое, более скоростное.
И здесь начинает действовать принцип “копейка рубль бережет”. На практике, в процессе анализа производительности, находится не одна, а множество таких тяжелых или немного тяжелых функций. В особо же сложных ситуациях приходится бороться буквально за каждую долю миллисекунды, находя новые пути оптимизации тех или иных фрагментов кода.
В идеале каждая функция в приложении должна стремиться работать как можно быстрее.
И в поиске таких слабых мест в коде хорошо помогают такие инструменты как Chrome Performance и React Profile. И тот и другой инструмент помогает записать определенный отрывок работы приложения и увидеть сколько времени потребовалось на отработку того или иного фрагмента кода, включая все его вложенные стеки.
Сам процесс анализа очень рутинный и кропотливый, поэтому лишний раз, из соображений профилактики, обычно к нему прибегать не стоит.
Прежде чем полагаться на пост-анализ производительности, вот несколько рекомендаций того как можно в принципе заблаговременно избежать появления тяжелых функций:
Используйте иммутабельность
В операциях сравнения два объекта по признаку нахождения в одной области памяти сравниваются в десятки, а то и в сотни раз быстрее, чем через перебор их свойств. Если вы будуте повсеместно применять иммутабельных подход к обновлению состояния, то и все операции сравнения в приложении будут работать многократно быстрее.
По возможности не используйте глубокое сравнение вообще
Функции глубокого (рекурсивного) сравнения объектов в большенство случаев работают крайне медленно. Если в вашем React-приложении возникает необходимость глубокого сравнения объектов, скорей всего что-то не так с вашей архитектурой.
Не выполняйте тяжелые расчеты дважды без необходимости
Если переменные, которые используются в расчетах, не изменились, не нужно производить расчеты второй раз. Используйте shouldComponentUpdate или простое if условие в getDerivedStateFromProps, componentDidUpdate, сравнив старые и новые props:
Грубо говоря, если само вычисление требует времени на выполнение больше, чем операция сравнения старого и нового значения аргументов для этого вычисления, то нужно сравнивать.
На то что бы сравнить два props нужно, приблизительно, 0.001 ms.
В данном случае вам так же может помочь мемоизация. Кешируйте результаты работы функций. Используйте React.pureComponent, Reselect, Memoize-one, React.memo, хуки useState, useMemo.
Для решения простых задач используйте нативный API
Не используйте слишком универсальные или многофункциональные библиотеки ради решения мелочных задач. Грубо говоря, если вам нужно отформатировать дату в строку “28/11/2019”, то используйте встроенный метод toLocaleDateString:
А не тащите в приложение библиотеку moment.
Не работайте с DOM API напрямую
Помните, что все операции с DOM тяжелые. Не стоит пытаться работать с DOM API напрямую без крайней необходимости. И тем более не пытайтесь использовать jQuery и прочие архаические инструменты в паре с React.
Знайте “цену” методов и операторов Javascript
- Метод массива forEach работает быстрее императивного for .. in.
- Добавление нового свойства в пустой объект дороже, чем создание нового объекта с этим свойством.
- === строгое сравнение работает быстрее не строгого ==
- и т.д.
— и это нужно просто знать.
Используйте продакшен сборку
Собирая приложение для публикации, используйте переменную окружения nodejs NODE_END со значением production .
Многие библиотеки, и React в т.ч., встраивают в свой код различные расширения и хуки, которые облегчают процесс разработки и отладки приложения, но снижают его производительность.
Поскольку эти расширения нужны только для разработки, они и встраиваются только в режиме разработки (когда NODE_ENV !== ‘production’). В режиме продакшен они не поставляются.
Так, например, React в зависимости от режима разработки в целом использует разные входные точки в библиотеку.
Если вы собираете приложение с переменной окружения NODE_ENV в значении production вы заметите повышение его производительности, и одновременно с этим, дополнительные инструменты отладки, такие как React DevTools, станут нерабочими. И наоборот.
Многие популярные бойлерплейты из коробки поддерживают разные режимы сборки в зависимости от назначения. Если вы пишите приложение с нуля, вы сами должны позаботится о выставлении правильного значения переменной окружения NODE_END development — для режима разработки, и production — при публикации.
React использует несколько умных подходов для минимизации количества дорогостоящих DOM-операций, необходимых для обновления пользовательского интерфейса. Для многих приложений, использование React приведёт к быстрому пользовательскому интерфейсу без особых усилий по оптимизации производительности. Тем не менее, существует несколько способов ускорить React-приложение.
Если вы испытываете проблемы с производительностью в React-приложении, убедитесь в том, что вы проводите тесты с настройками минифицированной продакшен-сборки.
По умолчанию в React есть много вспомогательных предупреждений, очень полезных при разработке. Тем не менее, они делают React больше и медленнее, поэтому вам обязательно следует использовать продакшен-версию при деплое приложения.
Если вы не уверены в том, что процесс сборки настроен правильно, вы можете проверить это, установив React Developer Tools for Chrome. Если вы посетите сайт, работающий на React в продакшен-режиме, иконка будет с чёрным фоном:
Если вы посетите сайт с React в режиме разработки, у иконки будет красный фон:
Как правило, режим разработки используется во время работы над приложением, а продакшен-режим при деплое приложения для пользователей.
Ниже вы можете найти инструкцию по сборке своего приложения для продакшена.
Create React App
Если ваш проект сделан с помощью Create React App, выполните:
Эта команда создаст продакшен-сборку вашего приложения в папке build/ вашего проекта.
Помните, что это необходимо только перед деплоем на продакшен. Для обычной разработки используйте npm start .
Мы предлагаем готовые для продакшена версии React и React DOM в виде отдельных файлов:
Помните, что для продакшена подходят только те файлы, которые заканчиваются на .production.min.js .
Для наиболее эффективной продакшен-сборки с Brunch, установите плагин terser-brunch .
Затем, для создания продакшен сборки, добавьте флаг -p к команде build :
Помните, что это нужно делать только для продакшен-сборки. Вам не нужно использовать флаг -p или применять этот плагин во время разработки, потому что это скроет вспомогательные предупреждения React и замедлит процесс сборки.
Для наиболее эффективной продакшен-сборки с Browserify, установите несколько плагинов:
При создании продакшен-сборки, убедитесь, что вы добавили эти пакеты для преобразования (порядок имеет значение):
- Плагин envify обеспечивает правильную среду для сборки. Сделайте его глобальным ( -g ).
- Плагин uglifyify удаляет импорты, добавленные при разработке. Сделайте его глобальным ( -g ).
- Наконец, полученная сборка отправляется к terser для минификации (прочитайте, зачем это нужно).
Помните, что это нужно делать только для продакшен-сборки. Вам не следует использовать эти плагины в процессе разработки, потому что это скроет вспомогательные предупреждения React и замедлит процесс сборки.
Для наиболее эффективной продакшен-сборки с Rollup, установите несколько плагинов:
При создании продакшен-сборки, убедитесь, что вы добавили эти плагины (порядок имеет значение):
- Плагин replace обеспечивает правильную среду для сборки.
- Плагин commonjs обеспечивает поддержку CommonJS в Rollup.
- Плагин terser сжимает и оптимизирует финальную сборку.
Полный пример настройки можно посмотреть здесь.
Помните, что это нужно делать только для продакшен-сборки. Вам не следует использовать плагин terser или плагин replace со значением 'production' в процессе разработки, потому что это скроет вспомогательные предупреждения React и замедлит процесс сборки.
Примечание:
Если вы используете Create React App, пожалуйста, следуйте инструкциям выше.
Этот раздел подойдёт для тех, кто самостоятельно настраивает webpack.
Webpack 4.0 и выше по умолчанию минифицирует код в продакшен-режиме.
Вы можете узнать об этом больше в документации webpack.
Помните, что это нужно делать только для продакшен-сборки. Вам не стоит использовать TerserPlugin в процессе разработки, потому что тогда скроются вспомогательные предупреждения React и замедлится процесс сборки.
Анализ производительности компонентов с помощью инструмента разработки «Profiler»
Пакеты react-dom версии 16.5+ и react-native версии 0.57+ предоставляют расширенные возможности анализа производительности в режиме разработки с помощью инструментов разработчика React Profiler. Обзор профайлера можно найти в посте блога «Введение в React Profiler». Пошаговое видео-руководство также доступно на YouTube.
Если вы ещё не установили инструменты разработчика React, вы можете найти их здесь:
Примечание
Профилирование продакшен-пакета для react-dom также доступно как react-dom/profiling . Подробнее о том, как использовать этот пакет, читайте на fb.me/react-profiling
Примечание
До React 17 мы использовали стандартный User Timing API для профилирования компонентов с помощью вкладки Performance браузера Chrome. Более подробнее об этом способе можно узнать в статье Бена Шварца (Ben Schwarz).
Виртуализация длинных списков
Если ваше приложение рендерит длинные списки данных (сотни или тысячи строк), мы рекомендуем использовать метод известный как «оконный доступ». Этот метод рендерит только небольшое подмножество строк в данный момент времени и может значительно сократить время, необходимое для повторного рендера компонентов, а также количество создаваемых DOM-узлов.
react-window и react-virtualized — это популярные библиотеки для оконного доступа. Они предоставляют несколько повторно используемых компонентов для отображения списков, сеток и табличных данных. Если вы хотите использовать что-то более специфическое для вашего конкретного случая, то вы можете создать собственный компонент с оконным доступом, как это сделано в Twitter.
React создаёт и поддерживает внутреннее представление отображаемого пользовательского интерфейса. Оно также включает React-элементы возвращаемые из ваших компонентов. Это представление позволяет React избегать создания DOM-узлов и не обращаться к текущим без необходимости, поскольку эти операции могут быть медленнее, чем операции с JavaScript-объектами. Иногда его называют «виртуальный DOM», но в React Native это работает точно так же.
Когда изменяются пропсы или состояние компонента, React решает нужно ли обновление DOM, сравнивая возвращённый элемент с ранее отрендеренным. Если они не равны, React обновит DOM.
Несмотря на то, что React обновляет только изменённые DOM-узлы, повторный рендеринг всё же занимает некоторое время. В большинстве случаев это не проблема, но если замедление заметно, то вы можете всё ускорить, переопределив метод жизненного цикла shouldComponentUpdate , который вызывается перед началом процесса ререндеринга. Реализация этой функции по умолчанию возвращает true , указывая React выполнить обновление:
Если вы знаете ситуации, в которых ваш компонент не нуждается в обновлении, вы можете вернуть 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 следующим образом:
В этом коде shouldComponentUpdate — это простая проверка на наличие каких-либо изменений в props.color или state.count . Если эти значения не изменяются, то компонент не обновляется. Если ваш компонент стал более сложным, вы можете использовать аналогичный паттерн «поверхностного сравнения» между всеми полями props и state , чтобы определить должен ли обновиться компонент. Этот механизм достаточно распространён, поэтому React предоставляет вспомогательную функцию для работы с ним — просто наследуйтесь от React.PureComponent . Поэтому, следующий код — это более простой способ добиться того же самого эффекта:
В большинстве случаев вы можете использовать React.PureComponent вместо написания собственного shouldComponentUpdate . Но он делает только поверхностное сравнение, поэтому его нельзя использовать, если пропсы и состояние могут измениться таким образом, который не сможет быть обнаружен при поверхностном сравнении.
Это может стать проблемой для более сложных структур данных. Например, вы хотите, чтобы компонент ListOfWords отображал список слов, разделённых через запятую, с родительским компонентом WordAdder , который позволяет кликнуть на кнопку, чтобы добавить слово в список. Этот код работает неправильно:
Проблема в том, что PureComponent сделает сравнение по ссылке между старыми и новыми значениями this.props.words . Поскольку этот код мутирует массив words в методе handleClick компонента WordAdder , старые и новые значения this.props.words при сравнении по ссылке будут равны, даже если слова в массиве изменились. ListOfWords не будет обновляться, даже если он содержит новые слова, которые должны быть отрендерены.
Сила иммутабельных данных
Лучший способ решения этой проблемы — избегать мутирования значений, которые вы используете как свойства или состояние. К примеру, описанный выше метод handleClick можно переписать с помощью concat следующим образом:
ES6 поддерживает синтаксис расширения для массивов, который поможет сделать это проще. Если вы используете Create React App, то этот синтаксис доступен там по умолчанию.
Таким же образом вы можете переписать код, который мутирует объекты. К примеру, мы имеем объект с именем colormap и хотим написать функцию, которая изменяет colormap.right на 'blue' . Мы могли бы написать:
Чтобы написать это без мутирования исходного объекта, мы можем использовать метод Object.assign:
Функция updateColorMap теперь возвращает новый объект, вместо того, чтобы мутировать исходный. Метод Object.assign входит в ES6 и требует полифила.
Синтаксис расширения свойств объекта упрощает обновление объектов без мутаций:
Этот синтаксис был добавлен в JavaScript в ES2018.
Если вы используете Create React App, то Object.assign и синтаксис расширения объектов доступны вам по умолчанию.
При работе с глубоко вложенными объектами, постоянное их обновление может запутать. Если вы столкнулись с такой проблемой, обратите внимание на Immer или immutability-helper. Эти библиотеки позволяют писать хорошо читаемый код, не теряя преимуществ иммутабельности.
Внутри React использует несколько умных подходов, чтобы минимизировать количество дорогостоящих операций DOM, необходимых для обновления пользовательского интерфейса. Для многих приложений использование React приведет к созданию быстрого пользовательского интерфейса, не выполняя много работы, чтобы специально оптимизировать производительность. Тем не менее, есть несколько способов ускорить ваше приложение React.
3.5.1 Использование сборки Production
Если вы проводите бенчмаркинг или испытываете проблемы с производительностью в своих приложениях React, убедитесь, что вы работаете с минифицированной production-сборкой.
По умолчанию React содержит много полезных предупреждений. Эти предупреждения очень полезны в разработке. Тем не менее, они делают React-приложение больше и медленнее, поэтому вы должны использовать production-версию при развертывании приложения.
Если вы не уверены, правильно ли настроен процесс сборки, вы можете проверить это, установив React Developer Tools для Chrome. Если вы заходите на сайт с React в режиме production, значок будет иметь темный фон:
Если вы заходите на сайт с React в режиме разработки, значок будет иметь красный фон:
Ожидается, что вы используете режим development при работе с вашим приложением и режим production при развертывании приложения для пользователей.
Ниже вы можете найти инструкции по созданию своего приложения для production.
3.5.2 Создание приложения React
Если ваш проект построен с помощью приложения Create React , запустите:
Это создаст production-сборку вашего приложения в папке build/ вашего проекта.
Помните, что это необходимо только перед развертыванием в production. Для нормальной разработки используйте npm start.
3.5.3 Однофайловые сборки
Предлагаются готовые версии React и React DOM в виде отдельных файлов:
Помните, что только файлы React, заканчивающиеся на .production.min.js , подходят для production.
3.5.4 Бранч (Brunch)
Для самой эффективной production-сборки бранча установите плагин uglify-js-brunch:
Затем, чтобы создать production-сборку, добавьте флаг -p в команду сборки:
Помните, что вам нужно сделать это только для production сборок. Вы не должны передавать флаг -p или применять этот плагин в разработке, потому что он скроет полезные предупреждения React и сделает сборки намного медленнее.
3.5.5 Browserify
Для наиболее эффективной сборки сборки Browserify установите несколько плагинов:
Чтобы создать production-сборку, убедитесь, что вы добавили эти преобразования (порядок имеет значение):
- Преобразование envify обеспечивает правильную среду сборки. Сделайте его глобальным ( -g ).
- Преобразование uglifyify устраняет импорты, добавленные на стадии разработки. Сделайте его глобальным ( -g ).
- Наконец, результирующая связка передается по каналу в uglify-js для минификации.
Помните, что вам нужно сделать это только для production сборок. Вы не должны применять эти плагины в разработке, потому что они будут скрывать полезные предупреждения React и делать сборки намного медленнее.
3.5.6 Rollup
Для наиболее эффективной production-сборки Rollup установите несколько плагинов:
Чтобы создать сборку, убедитесь, что вы добавляете эти плагины (порядок имеет значение):
- Плагин replace обеспечивает правильную среду сборки.
- Плагин commonjs обеспечивает поддержку CommonJS в Rollup.
- Плагин uglify сжимает и управляет финальной связкой (бандлом).
Полный пример установки смотреть здесь .
Помните, что вам нужно сделать это только для production сборок. Вы не должны применять плагин uglify или плагин replace со значением «production» в разработке, потому что они будут скрывать полезные предупреждения React и делать сборки намного медленнее.
3.5.7 webpack
Внимание! Если вы используете приложение Create React, следуйте приведенным выше инструкциям . Этот раздел имеет значение, только если вы конфигурируете webpack напрямую.Для создания наиболее эффективной webpack production сборки обязательно включите эти плагины в свою production конфигурацию:
Об этом вы можете узнать в документации по webpack .
Помните, что вам нужно только сделать это для production сборок. Вы не должны применять UglifyJsPlugin или DefinePlugin со значением 'production' в разработке, потому что они будут скрывать полезные предупреждения React и делать сборки намного медленнее.
3.5.8 Профилирование компонентов с помощью вкладки «Производительность Chrome»
В режиме development вы можете визуализировать, как компоненты монтируются, обновляются и демонтируются, используя инструменты производительности в поддерживаемых браузерах. Например:
Чтобы сделать это в Chrome:
Обратите внимание, что цифры относительны, поэтому компоненты будут быстрее отрисовываться в production. Тем не менее, это должно помочь вам понять, когда не связанный пользовательский интерфейс обновляется по ошибке, а также насколько глубоко и насколько часто обновляются ваши пользовательские интерфейсы.
В настоящее время Chrome, Edge и IE являются единственными браузерами, поддерживающими эту функцию, но можно использовать стандартный User Timing API .
3.5.9 Профилирование компонентов с помощью профайлера DevTools
react-dom 16.5+ и react-native 0.57+ предоставляют расширенные возможности профилирования в режиме DEV с помощью React DevTools профайлера. Обзор профайлера можно найти в главе обновлений. Видео-пошаговое руководство по профайлеру также доступно на YouTube .
Если вы еще не установили React DevTools, вы можете найти его здесь:
Профайлинг продакшен бандла для react-dom также доступен как react-dom/profiling . Подробнее о том, как использовать этот пакет, читайте на сайте fb.me/react-profiling.
3.5.10 Виртуализация длинных списков
Если ваше приложение отображает длинные списки данных (сотни или тысячи строк), мы рекомендуем использовать метод, известный как «экранирование». Этот метод отрисовывает только небольшое подмножество ваших строк в данный момент времени и может значительно сократить время, необходимое для повторной переотрисовки компонентов, а также количество создаваемых узлов DOM.
react-window и react-virtualized - популярные библиотека для экранирования. Они предоставляют различные повторно используемые компоненты для отображения списков, гридов и табличных данных. Вы также можете создать свой собственный экранирующий компонент, как это сделано в Twitter, если желаете что-то более специфическое для вашего конкретного случая.
3.5.11 Избежание согласования
React строит и поддерживает внутреннее представление отображаемого пользовательского интерфейса. Оно включает элементы React, которые вы возвращаете из своих компонентов. Это представление позволяет React избегать создания узлов DOM и доступа к существующим узлам без необходимости, поскольку это может быть гораздо медленнее, чем те же операции над простыми объектами JavaScript. Иногда его называют «виртуальным DOM», и оно работает аналогично в React Native.
Когда свойства или состояние компонента изменяются, React решает, требуется ли фактическое обновление DOM, сравнивая вновь возвращенный элемент с ранее отображаемым. Когда они не равны, React обновит DOM.
Вы можете визуализировать эти перерисовки виртуального DOM с помощью React DevTools:
В консоли разработчика выберите параметр « Highlight Updates » на вкладке « React »:
Взаимодействуя со своей страницей, вы должны увидеть, что вокруг любых компонентов, которые были переотрисованы, мгновенно появляются цветные границы. Это позволяет вам выявлять повторные отрисовки, которые не были необходимыми. Вы можете узнать больше о функции React DevTools из этого поста в блоге от Ben Edelstein .
Рассмотрим этот пример:
Обратите внимание, что когда мы вводим второе todo, первое todo также мигает на экране при каждом нажатии клавиши. Это означает, что он повторно отрисовывается React-ом вместе с элементом input. Это иногда называют «напрасной/бесполезной» отрисовкой. То есть мы знаем, что повторная отрисовка необязательна, так как контент первого todo не изменился. Но React об этом не знает, из-за чего и возникает такой эффект.
Несмотря на то, что React обновляет только измененные узлы DOM, переотрисовка все же занимает некоторое время. Во многих случаях это не вызывает проблем, но если замедление заметно, вы можете все это ускорить, переопределив метод жизненного цикла shouldComponentUpdate() , который запускается до начала процесса повторной отрисовки. Реализация этой функции по умолчанию возвращает true , указывая React выполнить обновление:
Если вы знаете, что в некоторых ситуациях ваш компонент не нуждается в обновлении, вы можете вместо этого вернуть false из shouldComponentUpdate , чтобы пропустить весь процесс отрисовки, включая вызов render() для этого компонента и ниже по иерархии.
В большинстве случаев вместо записи shouldComponentUpdate() вручную вы можете наследоваться от React.PureComponent . Это эквивалентно реализации shouldComponentUpdate() с неглубоким сравнением текущих и предыдущих props и state .
3.5.12 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 должен был делать DOM-изменения только для C6 , что было неизбежно. Для C8 этого удалось избежать сравнением отрисовываемых элементов React, а для поддеревьев C2 и C7 , даже не пришлось сравнивать элементы, так как нас выручил shouldComponentUpdate и отрисовка не вызвалась.
3.5.13 Примеры
Если единственный способ изменения вашего компонента – когда переменная props.style или state.value изменяется, вы могли бы выполнить проверку в shouldComponentUpdate как:
В этом коде shouldComponentUpdate просто проверяет, есть ли какие-либо изменения в props.style или state.value . Если эти значения не изменяются, компонент не обновляется. Если ваш компонент стал более сложным, вы можете использовать аналогичную схему «поверхностного сравнения» между всеми полями props и state , чтобы определить, должен ли компонент обновляться.
Этот шаблон настолько распространен, так что React предоставляет помощника для использования данной логики - просто наследуйтесь от React.PureComponent . Таким образом, следующий код - более простой способ добиться того же эффекта:
В большинстве случаев вы можете использовать React.PureComponent вместо написания собственного shouldComponentUpdate . Он делает только неглубокое сравнение, поэтому вы не можете использовать его, если props или state могут быть изменены таким образом, что нечеткое сравнение будет пропущено.
Это может быть проблемой для более сложных структур данных. Предположим, что вы хотите, чтобы компонент UserList отображал список пользователей, разделенных запятыми, с родительским компонентом UserAdmin , который позволяет вам щелкнуть кнопку, чтобы добавить очередного пользователя в список. Этот код работает неправильно:
3.5.14 Мощь неизменяющихся данных
Самый простой способ избежать этой проблемы - избежать изменения значений, которые вы используете в качестве props или state . Например, описанный выше метод onAddUser можно переписать с помощью concat вот так:
ES6 поддерживает spread синтаксис для массивов, который может сделать это проще. Если вы используете приложение Create React App , этот синтаксис доступен по умолчанию.
Вы также можете переписать код, который изменяет объекты, чтобы избежать изменения, аналогичным образом. Предположим, что у нас есть объект с именем user , и мы хотим написать функцию, которая устанавливает user.email в передаваемое значение. Мы могли бы написать:
Чтобы написать это без изменения исходного объекта, мы можем использовать метод Object.assign :
updateUserEmail теперь возвращает новый объект, а не изменяет старый. Object.assign входит в ES6 и требует полифила.
JavaScript предоставляет spread оператор для добавления свойств объекта, чтобы упростить его обновление без изменения:
Если вы используете приложение Create React, по умолчанию доступны как Object.assign , так и синтаксис spread для объектов.
3.5.15 Использование неизменяемых структур данных
Immutable.js - еще один способ решить эту проблему. Он предоставляет неизменные, постоянные коллекции, которые работают через совместное использование структуры:
- Неизменяемость: после создания коллекция не может быть изменена в любой другой момент времени.
- Постоянство: новые коллекции могут быть созданы из предыдущей коллекции и изменения, такого как set . Оригинальная коллекция по-прежнему действительна после создания новой коллекции.
- Совместное использование структуры: новые коллекции создаются с использованием такой же структуры, как и исходная коллекция, что позволяет сократить количество копий до минимума для повышения производительности.
Неизменность делает отслеживание изменений дешевым. Изменение всегда приведет к созданию нового объекта, поэтому нам нужно только проверить, изменилась ли ссылка на объект. Например, в этом обычном JavaScript-коде:
Несмотря на то, что b был отредактирован, поскольку это ссылка на тот же объект, что и a , это сравнение возвращает true . Вы можете написать аналогичный код с immutable.js:
В этом случае, поскольку при изменении a возвращается новая ссылка, мы можем с уверенностью сказать, что a изменился.
Есть две другие библиотеки, которые могут помочь использовать неизменяемые данные: seamless-immutable и immutability-helper .
Неизменяемые структуры данных предоставляют вам дешевый способ отслеживания изменений объектов, и все, что вам нужно для реализации shouldComponentUpdate . Это может дать вам хороший прирост производительности.
От автора: Оптимизация производительности приложений является ключевым моментом для разработчиков, которые заботятся о том, чтобы оценка пользователей оставалась положительной и чтобы они продолжали пользоваться приложением.
Согласно исследованию Akamai, секундная задержка во времени загрузки может привести к снижению конверсии на 7%, что заставляет разработчиков создавать приложения с оптимизированной производительностью.
Для приложений, созданных с помощью React, нам по умолчанию гарантируется очень быстрый пользовательский интерфейс. Однако по мере роста приложения разработчики могут столкнуться с некоторыми проблемами производительности.
В этом руководстве мы обсудим пять важных способов оптимизации производительности приложения React, включая методы предварительной оптимизации. Они включают:
Сохранение состояния компонента локальным, где это необходимо
React JS. Основы
Изучите основы ReactJS на практическом примере по созданию учебного веб-приложения
Компоненты Memoizing React для предотвращения ненужных повторных рендеров
Разделение кода в React с помощью динамического импорта
Виртуализация окон или списков в React
Ленивая загрузка изображений в React
Техники предварительной оптимизации React
Перед оптимизацией приложения React мы должны понять, как React обновляет пользовательский интерфейс и как измерять производительность приложения. Это упрощает решение любых проблем с производительностью React. Начнем с обзора того, как обновляется пользовательский интерфейс React.
Понимание того, как React обновляет пользовательский интерфейс
Когда мы создаем визуализированный компонент, React создает виртуальную DOM для дерева элементов в компоненте. Когда состояние компонента изменяется, React воссоздает виртуальное дерево DOM и сравнивает результат с предыдущим рендерингом.
React использует концепцию виртуальной DOM, чтобы минимизировать затраты на производительность повторного рендеринга веб-страницы, потому что фактическая DOM требует больших затрат на манипулирование.
Это здорово, потому что ускоряет время рендеринга пользовательского интерфейса. Однако эта концепция также может замедлить работу сложного приложения, если оно не сконфигурировано должным образом.
Мы можем сделать вывод, что изменение состояния в компоненте React вызывает повторную визуализацию. Точно так же, когда состояние передается дочернему компоненту в качестве свойства, оно перерисовывается в дочернем компоненте и так далее, что нормально, потому что React должен обновлять пользовательский интерфейс.
Проблема возникает, когда на дочерние компоненты не влияет изменение состояния. Другими словами, они не получают никаких свойств от родительского компонента.
Тем не менее React повторно визуализирует эти дочерние компоненты. Итак, пока родительский компонент выполняет повторную визуализацию, все его дочерние компоненты выполняют повторную визуализацию независимо от того, передается ли им свойство или нет, это поведение React по умолчанию.
Давайте продемонстрируем эту концепцию. Например, у нас есть компонент App, содержащий некоторое состояние и дочерний компонент:
Читайте также: