В жизненном цикле компонента React компонент перерисовывается при обновлении. Это происходит, если обнаружены различия (т. н. difs) между виртуальным DOM React и DOM в браузере, в этом случае компоненты, содержащие difs (и любые дочерние компоненты этих компонентов), перерисовываются в браузере. Если эта концепция вам незнакома или вы хотите освежить в памяти функциональность виртуального DOM React, ознакомьтесь с этой темой отдельно.
Хотя эта функциональность является частью того, что делает React таким замечательным, она может легко снизить производительность вашего приложения, если у вас есть компоненты, содержащие дорогостоящие функции, которые перерисовываются из-за их связи с другими. Кроме того, поскольку React ищет изменения в ваших компонентах, могут быть обнаружены непреднамеренные различия, возникающие из-за того, как JavaScript обрабатывает поверхностные сравнения и равенства, что приводит к избыточному перерисовыванию.
Если ваши компоненты включают в себя дорогостоящие операции (например, длинную функцию цикла или извлечение больших объемов данных из API), эти ненужные/неожиданные повторные рендеры могут существенно повлиять на производительность и привести к плохому пользовательскому опыту. React представил концепцию memo, чтобы исправить это.
Введение в меморизацию
Меморизация — это метод оптимизации, который используется для увеличения скорости программ путем сохранения результатов вызовов дорогостоящих функций в кэше (чтобы они эффективно «запоминались») и возврата этих результатов при повторном предоставлении тех же параметров. Когда этот метод применяется к функции, функция меморизируется.
В качестве простого примера предположим, что у нас есть функция, которая принимает целое число в качестве аргумента и возвращает этот ввод, умноженный на 2. Мы передаем 2 в качестве аргумента для вызова функции, а возвращается 4. Если бы эта функция использовала меморизацию каждый раз, когда 2 передается в качестве аргумента для вызова функции, результат был бы запомнен и возвращен без необходимости выполнять выражение внутри функции.
Хотя приведенный выше пример очень прост, вы, вероятно, можете себе представить, какую большую пользу принесет меморизация при использовании более сложной и дорогостоящей функции в контексте повторно отображаемых компонентов.
Повышение производительности с помощью useMemo
useMemo — это хук React, который может помочь повысить производительность приложения за счет меморизации или сохранения результата ресурсоемких функций и отображения этого сохраненного результата при повторном рендеринге компонента без необходимости повторного запуска функции, если только не изменятся входные данные, предоставленные функции (в этом случае функция перезапускается).
Теперь, как выглядит useMemo? Ссылаясь на документацию React, useMemo выражается следующим образом:
const memoizedValue = React.useMemo(() => computeExpensiveValue(a, b), [a, b]);
Как показано выше, useMemo принимает функцию и массив зависимостей. Эти зависимости играют аналогичную роль аргументам внутри функции. Элементы в списке зависимостей — это то, на что обращает внимание useMemo: если нет различий между зависимостями до и во время повторного рендеринга, то результат функции останется прежним. Если есть различия в зависимостях, то функция будет запущена повторно во время повторного рендеринга. Как вы можете себе представить, это может значительно сократить расходы на повторный рендеринг компонентов, поскольку весь компонент может быть повторно отрендерен без необходимости повторного запуска функции (если зависимости остаются прежними), вместо этого сохраненный результат возвращается при повторном рендеринге компонента.
Основной вариант использования useMemo — это обёртывание больших и дорогих функций, которые замедлят производительность приложения, если будут повторно запускаться при каждом повторном рендеринге.
Вот пример применения useMemo к массиву элементов, который использует две дорогие функции:
const Cats = React.useMemo(() =>
allTheCats.map(cat => ({
...cat,
catProp1: firstExpensiveFunction(props.first),
catProp2: secondExpensiveFunction(props.second)
})), [allTheCats]
)
Здесь функция useMemo будет запущена при первом рендеринге (поскольку зависимости будут новыми), поток будет заблокирован до тех пор, пока не завершатся две дорогие функции. В последующих повторных рендерингах компонентов эти дорогие функции не нужно будет запускать повторно, учитывая, что allTheCats не изменился. Результаты первоначального вызова функции будут сохранены и доступны из переменной Cats и останутся прежними до тех пор, пока не изменятся зависимости.
В результате пользовательский опыт будет таким, как если бы эти дорогостоящие функции отображались мгновенно, что делает применение useMemo предпочтительным при наличии в компоненте дорогостоящих синхронных функций.
Что нужно иметь в виду
При написании кода лучше всего сначала написать функции, а затем вернуться к ним, чтобы посмотреть, можно ли провести рефакторинг для оптимизации производительности. Слишком частое использование useMemo в вашем приложении может негативно сказаться на производительности.
Чтобы определить, следует ли вам включать useMemo , вы можете использовать инструменты профилирования для обнаружения проблем производительности, которые можно улучшить с помощью useMemo. Примером того, когда следует использовать меморизацию, может служить функция, в которой вы определяете значительное количество переменных (затраты на память).
Помните, что хук useMemo может повысить производительность приложения, но также может замедлить его работу, если его использовать слишком часто, поскольку чем больше он используется, тем больше памяти приходится выделять приложению.