Кеширования в React - все ли так однозначно?
Tags: react, memoization, cash, performance
Published: 2024-07-06 10:55:05

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

_Начнем издалека..._

## Зачем вообще использовать кеширование?
Когда мы пишем маленькое приложение в виде учебного TODO листа или нескольких страниц с небольшим числом компонентов, может показаться, что кеширование не нужно. Интерфейс отзывчивый, а React настолько классный и оптимизированный, что не трогает DOM дерево так, как чистый JavaScript, делая точечные перерисовки. Но всё меняется, когда проекты становятся большими и сложными (много разметки, локиги, зависимостей и условных рендеров), а ваше приложение начинают открывать на устройствах с менее мощным железом, чем ваше рабочее.

Кеширование в React используется для оптимизации производительности приложений. Оно позволяет снизить нагрузку на CPU и видеокарту, используя больше ресурсов памяти. В современных устройствах зачастую больше доступной памяти, чем ресурсов CPU или видеокарты, поэтому кеширование становится эффективным методом оптимизации. Перерисовки компонентов требуют значительных вычислительных ресурсов для выполнения различных операций, таких как создание виртуального DOM, сравнение его с предыдущим состоянием и обновление реального DOM. Это может существенно замедлить работу приложения, особенно если речь идет о сложных вычислениях или большом количестве элементов. Кеширование же позволяет избежать этих затрат за счет хранения результатов предыдущих вычислений в памяти и использования их при необходимости. Таким образом, благодаря кешированию можно значительно улучшить производительность приложения, сократив время, затрачиваемое на перерисовки, и улучшив общую отзывчивость пользовательского интерфейса.

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

## Хук useCallback()
Использую, когда необходимо передать функцию как callback пропсом в другой компонент. Он также полезен, если функция выполняет действительно сложные вычисления. Например, это может быть перебор большого массива, фильтрация данных, выполнение сложных математических расчетов или обработка данных из API. useCallback предотвращает создание новой функции при каждом рендере, что особенно важно для производительности, когда функция передается в дочерние компоненты, используемые в качестве обработчиков событий или callback'ов в компонентах, которые часто обновляются.

## Хук useMemo()
Использую для мемоизации результатов выполнения сложных вычислений, таких как перебор большого массива данных, сортировка, фильтрация, обработка данных из API или сложные математические расчеты. useMemo возвращает мемоизированное значение, которое пересчитывается только при изменении зависимостей, указанных в массиве зависимостей.

Несмотря на то, что useMemo не следует путать с React.memo, иногда можно встретить использование useMemo для мемоизации результатов рендера функционального компонента. Однако это не является распространённой практикой, так как сообщество React обычно использует React.memo для мемоизации компонентов. Следование общепринятым практикам делает ваш код более читабельным и понятным для других разработчиков. Например, функцию, возвращающую JSX, лучше оборачивать в React.memo, поскольку это более ожидаемо и понятно для тех, кто будет читать ваш код.

Подобно тому, как для функций изменения состояния, созданных с помощью useState, принято использовать префикс set (например, setCount), использование React.memo для компонентов является стандартом в сообществе React. Это помогает сохранять консистентность кода и облегчает его поддержку и понимание.

## Метод React.memo
Использую для обёртки компонентов, пропсы которых нечасто меняются. Это особенно полезно для переиспользуемых компонентов из `shared/ui` (вашего UI kit). Если компонент в качестве `props.children` принимает сложную структуру данных (другой компонент), а не примитив (строку или число), такой компонент не следует мемоизировать. Мемоизация занимает память, и сравнение примитивов затрачивает мало ресурсов. Однако в случае сложных объектов это дорого по ресурсам, так как сложные структуры данных (объекты, массивы) сравниваются по ссылке. Даже если такой объект в `props.children` аналогичен предыдущему, передается ссылка на другую ячейку памяти, и memo не сработает — компонент перерисуется, и ресурсы будут потрачены на проверку старого и нового значения.

Используйте мемоизацию с умом. Удачи, работяги!