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

В документации React есть только один намёк:

// useCallback(fn, deps) эквивалентен useMemo(() => fn, deps)

Что делает useMemo

useMemo вызывает функцию при изменении зависимостей и запоминает результат между рендерами.

Отличие от useCallback в том, что тот запоминает само значение (обычно определение функции) между рендерами, не вызывая его.

useMemo нужен, чтобы не повторять дорогостоящие вычисления при каждом рендере. useCallback — чтобы сохранять стабильную ссылку на функцию.

Когда использовать useMemo

Здесь легко запутаться. Большинство статей описывают useMemo и тут же объясняют, когда его не стоит использовать.

Не используй useMemo, пока не заметишь, что части приложения работают заметно медленно. «Преждевременная оптимизация — это корень всех зол», а расставлять useMemo везде подряд и есть преждевременная оптимизация.

Несколько случаев, когда useMemo оправдан:

  • ты замечаешь, что рендеринг компонента происходит очень медленно, и ты передаешь вычисление неизвестному количеству дочерних элементов, например: при рендеринге дочерних элементов с помощью Array.map()
  • твое приложение часто перестает отвечать, потому что ты получаешь большой объем данных и необходимо преобразовать их в пригодный для использования формат.

Главное - сосредоточиться на проблеме.

«Мое приложение работает медленно и требует больших вычислений» - это проблема, которую помогает решить useMemo. Запусти приложение через React DevTools Profiler, а также через Google Lighthouse или WebPageTest, чтобы понять показатели производительности, оберни свои вычисления в useMemo, а затем измерь ещё раз.

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

Почему бы тогда не использовать useMemo везде?

Это не бесплатная оптимизация производительности.

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

useMemo занимает память, чтобы освободить время процессора. Это имеет смысл, только если вычисления действительно нагружают CPU.

А как насчет стабильных ссылок?

Если ты хочешь сохранить стабильную ссылку на объект / массив, не требующий пересчета, рассмотри возможность использования useRef.

С другой стороны, если нужно пересчитать значение при изменении зависимостей, useMemo - это то, что тебе нужно.

Возможные ошибки при использовании useMemo

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

"You may rely on useMemo as a performance optimization, not as a semantic guarantee"

Ты можешь полагаться на useMemo как на оптимизацию производительности, а не как на семантическую гарантию.

Иными словами: кеш нестабилен!

Это означает, что useMemo нельзя использовать как часть бизнес-логики. Если значение обязано быть стабильным по смыслу, ищи другое решение: вынос вычислений, состояние, useRef или пересмотр архитектуры.

Итог

useMemo и useCallback нужны не для красоты, а для точечной оптимизации после измерений. Если проблема не подтверждена профилировщиком или поведением интерфейса, скорее всего, код станет только сложнее.