Все мы знаем про 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 не сработает — компонент перерисуется, и ресурсы будут потрачены на проверку старого и нового значения.

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

Комментарии (16)


  1. sovaz1997
    10.07.2024 12:45
    +1

    useCallback предотвращает создание новой функции при каждом рендере

    Не предотвращает. Дальше можно не читать


    1. Andrey098 Автор
      10.07.2024 12:45

      Привет! Спасибо за комментарий! Благодаря ему я еще раз сходил в документацию и проверил информацию. Ты ошибаешься - UseCallback нужен чтобы при каждом ререндное не создавалась новая функция, это полезно когда мы передаем такую функцию к качестве коллбэка пропсом в компонент который обернут в memo. Смысл хука не в том что ссылка на непримитивный тип данных (функцию) сохраняется а в том что происходит дальше - ссылка сохраняется и попадает в прост где строго равна предыдущей передаче в пропс


      1. sovaz1997
        10.07.2024 12:45
        +3

        Привет. Функция, тем не менее, будет создаваться каждый раз. Просто она будет выбрасываться, если зависимости не изменились)
        Так работает JavaScript. Функция каждый рендер будет создаваться и передаваться как первый аргумент useCallback.


        1. Andrey098 Автор
          10.07.2024 12:45

          Еще глубже вник в тему. Действительно, JavaScript инициализирует функцию при каждом рендере, но useCallback (при неизменных значениях в массиве зависимостей) не позволяет передать новую функцию дальше. Это также помогает сборщику мусора эффективнее управлять памятью, так как одна и та же функция повторно используется при каждом рендере.

          Лайк комментарию! Даже разработчики документации React упростили объяснение, написав: "useCallback is a React Hook that lets you cache a function definition between re-renders."


          1. Kergan88
            10.07.2024 12:45
            +1

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


    1. Deadform
      10.07.2024 12:45

      Автор перепутал с useRef, скорее всего)


  1. 4i4imek
    10.07.2024 12:45

    Получается, что однозначно.
    Мемоизация в реакте нужна только для трудоемких операций. Нет смысла оборачивать все подряд в memo().

    В этой статье я углублюсь в их работу...

    А где? Вроде бы информации не из документации особо нет. Ни кода, ни капота, ни многозначности(


    1. Andrey098 Автор
      10.07.2024 12:45

      Спасибо за комментарий! Это моя первая статья на хабре, буду тщательнее подбирать заголовки!
      Цель статьи - своими словами, просто изложить суть оптимизации Performance в React. Статья рассчитана для новичков, поэтому указан уровень - "Простой"


  1. SuperCat911
    10.07.2024 12:45
    +1

    Речь о мемоизации же. Причем тут кеширование?


    1. Andrey098 Автор
      10.07.2024 12:45

      Как я понимаю, кеширование в реакт это и есть мемоизация. Кеширование вне механизмов реакт тут не рассматривается


      1. SuperCat911
        10.07.2024 12:45
        +1

        Во-первых, спасибо за публикацию. Интересно.

        Во-вторых, комментарий по поводу memorization vs caching. Разница есть.

        Мемоизация — это особая форма кэширования, которая подразумевает кэширование возвращаемого значения функции на основе ее параметров.

        Кэширование — более общий термин; например, HTTP-кэширование — это кэширование, но не мемоизация.

        Ну, и реакт часто рассматривается не как библиотека рендеринга, а как фреймворк. Поэтому в вашем случае кеширование в реакте - это мемоизация.