{wcademy}

React: оптимизация рендеринга

October 13, 2020

Подходы к оптимизации

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

Внешний вид компонента, а именно результат вызова рендера, всегда должен основываться на комбинации пропсов и стейта. И никак иначе. Это позволяет заранее знать, что рендер компонента будет возвращать те же результаты, пока пропсы и стейт остаются неизменными. Это тесно связано с понятием чистоты функции. Если вкратце, то результат выполнения функции должен зависеть исключительно от её входных параметров и ни от чего больше, и пока они не меняются, всегда возвращает тот же результат. То же самое и в Реакте. Бо́льшая часть возможных оптимизаций Реакта основывается на предположении, что компоненты — чистые, и если их входные параметры не поменялись, то можно не вызывать рендер компонента и его вложенных компонентов.

Техники оптимизации

По умолчанию, Реакт предоставляет несколько встроенных возможностей для избежаний лишних вызовов рендера: - shouldComponentUpdate (для компонентов-классов) — метод жизненного цикла, вызываемый до вызова render, и возвращающий булев флаг о том, нужно ли перерисовывать компонент. Как проверять это — на совести программиста, но обычно в нём просто проверяют, изменились ли стейт или пропсы. - React.PureComponent (для компонентов-классов) — базовый класс для компонента, альтернатива стандартному React.Component, у которого определён метод shouldComponentUpdate. Он как раз автоматически сравнивает старые/новые стейт/пропсы, не самим же писать однообразные проверки? - React.memo() (для любых компонентов, но обычно функциональных) — компонент высшего порядка (Higher-order component, HOC), которым можно обернуть любой другой компонент. Реализует то же самое, что и React.PureComponent, но позволяет оптимизировать и функциональные компоненты.

Обычно во всех методах используют «поверхностное сравнивание» (shallow comparison). То есть если передаётся массив, то все его элементы сравниваются один за другим простым ===, если массив, то все его свойства. В теории можно вручную реализовать и глубокое сравнивание (deep comparison) всех вложенных объектов, но нужно аккуратно профайлить — почти всегда эта операция будет дороже, чем вызов render. Таким образом, проверяя на изменение стейт и пропсы, можно не вызывать рендер у компонента, как следствие рендер и всех его вложенных компонентов и чувствительно ускорить приложение.

Мемоизация ссылок

Мемоизация (запоминание, от англ. memoization) — в программировании сохранение результатов выполнения функций для предотвращения повторных вычислений.

<MyButton onClick={() => doSomething(id)}>Click me!</MyButton>

В чём проблема этого кусочка кода?

При каждом ререндере родительского компонента в MyButton будет улетать новая анонимная функция, несмотря на то, что, по сути, это абсолютно ссылка на абсолютно туже коллбэк функцию, И если мы хотим оптимизировать MyButton, то все усилия будут напрасны. По логике ничего не изменилось, но пропсы новые — перерисовка каждый раз.

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

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

Профайлинг

В React Developer Tools встроен профайлер, его можно найти в соответствующей вкладке. Он позволяет последовать вопрос того, как рендерится приложение. В настройках можно включить подсветку компонентов, когда вызывается их рендер, а также запись событий, которые привели к рендеру (во время профайлинга). очень рекомендую поставить обе галочки.

как включить профайлер в React DevTools

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

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

🚀  Если узнал из статьи что-то полезное, ставь лайк и подписывайся на наш канал в Телеграм или группу ВК. Обсудить статью можно в нашем уютном чатике 😏

© 2019 - 2022, {wcademy}