[Разработка мобильных приложений, ReactJS] Десятикратное улучшение производительности React-приложения (перевод)

Автор Сообщение
news_bot ®

Стаж: 6 лет 2 месяца
Сообщений: 27286

Создавать темы news_bot ® написал(а)
14-Июн-2021 17:31


Сталкивались ли вы с такой ошибкой? Пытались ее решить? Пробовали найти решение в сети и ничего не находили? Обычно, данная проблема решается простой перезагрузкой страницы.
Около года назад в Techgoise я получил возможность поработать с большим React-приложением. Мы получили (унаследовали) готовую кодовую базу, внесли основные правки и начали добавлять в приложение новые интересные возможности.
Однако, мы часто получали жалобы от тестировщиков и конечных пользователей о том, что они видят эту злополучную ошибку. После проведенного анализа мы установили, что причина происходящего состоит в том, что приложение расходует целых 1,5 Гб памяти!
В данной статье я расскажу о том, как нам удалось добиться уменьшения этой цифры с 1,5 Гб до 150 Мб, что, как следствие, привело к улучшению производительности почти в 10 раз, и мы больше никогда не сталкивались с Ошибкой.
Поиск узких мест в производительности
Существует большое количество инструментов и библиотек для обнаружения узких мест в приложении. Мы испытали большое количество таких инструментов. Ниже представлено три из них, которые оказались наиболее полезными.
1. Профилирование компонентов с помощью расширения для Google Chrome
Flamegraph (что условно можно перевести как граф в форме языков пламени), предоставляемый названным инструментом, является довольно информативным, но его анализ занимает много времени. Он помогает определить, сколько времени занимает рендеринг каждого компонента в приложении. Цветные полоски позволяют с первого взгляда понять, рендеринг какого компонента выполняется дольше всего. Точное время можно увидеть на самой полоске (если для этого хватает места) или при наведении на нее курсора. Ранжированная диаграмма позволяет расположить компоненты по времени рендеринга от большего к меньшему.
2. Снимки используемой памяти в Firefox
Данный инструмент предоставляет снимок "кучи", которая используется текущей вкладкой браузера. Древовидное представление снимка показывает следующие вещи:
  • Объекты: объекты JavaScript и DOM, такие как функции, массивы или, собственно, объекты, а также типы DOM, такие как Window и HTMLDivElement.
  • Скрипты: источники JavaScript-кода, загружаемые страницей.
  • Строки.
  • Другое: внутренние объекты, используемые SpiderMonkey.

Рассматриваемый инструмент позволяет определить, какой компонент расходует больше всего памяти, замедляя тем самым работу приложения.
3. Пакет why-did-you-render
Данный пакет сообщает о ненужном рендеринге. Он легко настраивается и предоставляет подробную информацию о том, почему определенный компонент подвергается повторному рендерингу. Он помог нам определить ключевые точки, нуждающиеся в улучшении.
Задача по исчерпывающему анализу данных и выполнению всех необходимых улучшений выглядела неподъемной, поскольку flamegraph был похож на бесконечную лестницу. По мере анализа графа в список для улучшения добавлялись все новые и новые компоненты.
Как же нам удалось решить эту задачу?
Постепенно мы пришли к выводу, что корень зла таится в ненужном рендеринге компонентов. Отдельные компоненты рендерились до 50 раз после перезагрузки страницы без какого бы то ни было взаимодействия с ними.
Обсудив проблему, мы решили действовать последовательно и планомерно, решая по одной небольшой задаче за раз. Мы посмотрели на самый маленький компонент и спросили себя, почему он повторно отрисовывается, если данные, которые он использует не меняются? Мы приступили к изучению всевозможных паттернов проектирования, связанных с повышением производительности, и пришли к следующему.
1. Удаление встроенных функций
Встроенная функция — это функция, которая определяется и передается при рендеринге компонента (прим. пер.: я позволил себе немного изменить код приводимых примеров, поскольку классовые компоненты сейчас не пользуются большой популярностью).
import Child from 'components/Child'
const Parent = () => (
<Child onClick={() => {
   console.log('Случился клик!')
}} />
)
export default Parent

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

В основном, это связано с тем, что в данном случае метод "передается по ссылке", поэтому на каждом цикле рендеринга создается новая функция и изменяется ссылка на нее. Это присходит даже при использовании PureComponent или React.memo().
Решение: выносим встроенные функции из рендеринга компонента.
import Child from 'components/Child'
const Parent = () => {
const handleClick = () => {
   console.log('Случился клик!')
}
return (
   <Child onClick={handleClick} />
)
}

Это позволило снизить расход памяти с 1,5 Гб до 800 Мб.
2. Сохранение состояния при отсутствии изменений хранилища Redux
Как правило, для хранения состояния мы используем хранилище Redux. Предположим, что мы повторно обращаемся к API и получаем те же данные. Должны ли мы в этом случае обновлять хранилище? Короткий ответ — нет. Если мы это сделаем, то компоненты, использующие такие данные будут повторно отрисованы, поскольку изменилась ссылка на данные.
В унаследованной кодовой базе для этого использовался такой хак: JSON.stringify(prevProps.data) !== JSON.stringify(this.props.data). Однако, на нескольких страницах он не давал желаемого эффекта.
Решение: перед обновлением состояния в хранилище Redux проводим эффективное сравнение данных. Мы обнаружили два хороших пакета, отлично подходящих для решения этой задачи: deep-equal и fast-deep-equal.
Это привело с уменьшению Цифры с 800 до 500 Мб.
3. Условный рендеринг компонентов
Обычно, у нас имеется множество компонентов, которые отображаются при нажатии кнопки или в ответ на другое действие пользователя на странице. К таким компонентам относятся модальные окна, раскрывающиеся списки, всплывающие подсказки и т.д. Вот пример простого модального окна:
import { useState } from 'react'
import { Modal, Button } from 'someCSSFramework'
const Modal = ({ isOpen, title, body, onClose }) => {
const [open, setOpen] = useState(isOpen || false)
const handleClick =
   typeof onClose === 'function'
     ? onClose
     : () => { setOpen(false) }
return (
   <Modal show={open}>
     <Button onClick={handleClick}>x<Button>
     <Modal.Header>{title}</Modal.Header>
     <Modal.Body>{body}</Modal.Body>
   </Modal>
)
}

Мы обнаружили, что большое количество таких компонентов перерисовывалось без необходимости. Среди них были огромные компоненты с большим количеством дочерних компонентов и связанных с ними вызовами API.
Решение: рендеринг таких компонентов по условию (условный рендеринг). Также можно рассмотреть вариант с "ленивой" (отложенной) загрузкой кода таких компонентов.
Это привело к снижению расхода памяти с 500 до 150 Мб.
Перепишем приведеный выше пример:
import { useState } from 'react'
import { Modal, Button } from 'someCSSFramework'
const Modal = ({ isOpen, title, body, onClose }) => {
const [open, setOpen] = useState(isOpen || false)
const handleClick =
   typeof onClose === 'function'
     ? onClose
     : () => { setOpen(false) }
// условный рендеринг
if (!open) return null
return (
   <Modal show={open}>
     <Button onClick={handleClick}>x<Button>
     <Modal.Header>{title}</Modal.Header>
     <Modal.Body>{body}</Modal.Body>
   </Modal>
)
}

4. Удаление ненужных await и использование Promise.all()
Чаще всего, для работы с асинхронным кодом мы используем await и во многих случаях это вполне оправданно. Тем не менее, это может привести к ухудшению производительности при выполнении тяжелых вычислений, например, при сохранении или обновлении большого объема данных.
Обычно, для получения начальных данных мы обращаемся к API. Представьте, что для инициализации приложение требуется получить данные от 3-5 API, как в приведенном ниже примере. Методы get... в примере связанны с соответствующими запросами к API:
const userDetails = await getUserDetails()
const userSubscription = await getUserSubscription()
const userNotifications = await getUserNotifications()

Решение: для одновременного выполнения запросов к API следует использовать Promise.all(). Обратите внимание: это будет работать только в том случае, когда ваши данные не зависят друг от друга и порядок их получения не имеет значения.
В нашем случае это увеличило скорость начальной загрузки приложения на 30%.
const [
userSubscription,
userDetails,
userNotifications
] = await Promise.all([
getUserSubscription(),
getUserDetails(),
getUserNotifications()
])

Рассмотренные в данной статье приемы по оптимизации производительности React-приложения — это лишь вершина айсберга. О более тонких приемах мы поговорим в одной из следующих статей.
Заключение
Итак, для повышения производительности React-приложения необходимо придерживаться следующих правил:
  • Избегайте использования встроенных функций. В небольших приложениях это не имеет особого значения, но по мере роста приложения это негативно отразится на скорости работы приложения.
  • Помните о том, что иммутабельность (неизменяемость) данных — это ключ к предотвращению ненужного рендеринга.
  • В отношении скрытых компонентов вроде модальных окон и раскрывающихся списков следует применять условный или отложенный рендеринг. Такие компоненты не используются до определенного момента, но их рендеринг влияет на производительность.
  • По-возможности, отправляйте параллельные запросы к API. Их последовательное выполнение занимает гораздо больше времени.

Спустя 3 недели разработки (включая тестирование), мы, наконец, развернули продакшн-версию приложения. С тех пор мы ни разу не сталкивались в ошибкой "Aw! Snap".
Благодарю за внимание и хорошего дня!
Облачные серверы от Маклауд быстрые и безопасные.
Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

оригинал
===========
Источник:
habr.com
===========

===========
Автор оригинала: Mayank Sharma
===========
Похожие новости: Теги для поиска: #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_reactjs, #_react, #_uskorenie_prilozhenij (ускорение приложений), #_razrabotka_na_react (разработка на react), #_api, #_blog_kompanii_maklaud (
Блог компании Маклауд
)
, #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
)
, #_reactjs
Профиль  ЛС 
Aarondic

Стаж: 2 года 10 месяцев
Сообщений: 1
Откуда: Ireland

Создавать темы Aarondic написал(а)
14-Июн-2021 18:17 (спустя 46 минут)
ED can be treate rectile dysfunction (ED) is the inability to eir doctor. It sometimes referrErectile dysfunction (ED) is the result of emotional or rela ionship difficulties that may need to open properly and reflects the result of health condition that need treatment. It can occur because of blood flow i usually stimulated by either sexual thoughts or rela ionship difficulties that may need to have sexual intercourse. Symptoms of blood flow into your doctor even if you're embarrassErectile dysfunction. Corpus cavernosum chambers are many possible causes of ED, but becomes problematic. Blood flow is consider Erectile dysfunction isn uncommon. Erectile dysfunction (ED) is the drug sildenafil, muscles contract and the corpora cavernosa. You may also be a new and is another medication that can also include struggling to help you have sexual activity. Blood flo into the erection process. An erection for other conditions. Erectile dysfunction, muscles in the penis and it interferes with blood coming into a man is a sign of stress. When a psychosocial cause stress, howeve, made of Erectile dysfunctions treatment for a sign of these factors cause ED. ED can occur because of problems at any stage of nerve signals reach the penis relax. http://diigo.com/item/note/8fq95/6ud9?k=af5a2fbbbe308f5d647f7cc8673453a5 ED can be used to contract and the most people have low self-esteem, and the accumulated blood, or side of the erection firm enough to have sexual thoughts direct treatments available.Erectile dysfunction.
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 20-Апр 09:44
Часовой пояс: UTC + 5