React ⚛️
September 27, 2023

Мемоизация в React (useMemo, useCallback, React.memo)

Мемоизация

Определение

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

Мемоизация в React

Проблема

При каждом ре-рендере компонент пересоздает переменные и функции объявленные внутри компонента. Хуки для мемоизации позволяют сохранять их между рендерами.

Решение

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

При первом рендере хук с пустыми зависимостями создает мемоизированное значение.

При повторных рендерах, при наличии зависимостей, хук решает:

  • Если зависимости до и после совпадают
    • значение не пересоздается
    • отдается предыдущее значение по ссылке
  • Если зависимости различаются
    • новое значение сохраняется в хранилище
    • отдается новое значение по ссылке

useCallback

Используется для мемоизации функций.

Причины использовать:

  • Уменьшение количества повторных рендеров
  • Предотвращение слишком частого срабатывания эффекта
  • Оптимизация пользовательского хука

Принцип работы:

const prevState = { 
  callback: null, 
  deps: null
} 

function useCallback(callback, deps) {

  // если deps не существует в prevState 
  // или в новых данных, сохраняем 
  // текущие параметры и возвращем callback.
  
  if (!prevState.deps || !deps) { 
    prevState.callback = callback
    prevState.deps = deps
    return callback
  }
  
  // Если deps существуют. Сравниваем 
  // какой-либо функцией массивы и если они совпадают, 
  // тогда возвращаем мемоизированную функцию

  if (shallowEqual(deps, prevState.deps)) { 
    return prevState.callback 
  }
  
  // Ну и если deps не совпадают, тогда снова 
  // сохраняем параметры и возвращаем текущий callback
  
  prevState.callback = callback
  prevState.deps = deps
  
  return callback
}

Важно помнить

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

Переменная или функция объявленая в родительском компоненте и проброшенная в дочерний — будет всегда считаться новой. Поэтому, кроме useCallback в родительском компоненте, следует использовать React.memo в дочернем, например:

import { memo } from 'react';

const List = memo(function List({ items }) {});

function TodoList({ todos, tab, theme }) {  
  // используйте useMemo, чтобы данные кэшировались
  // между повторными рендерингами
  // до тех пор, пока зависимости не изменятся...  
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab), [todos, tab]  
  );  
    
  // List получит одинаковые свойства и пропустит ре-рендер 
  return (
    <div className={theme}>
      <List items={visibleTodos} />   
    </div>
  );
}

Потеря ссылки на мемоизированный объект так же отключает оптимизацию:

const Chart = memo(function Chart({onClick}) { 
  return (   
    <div onClick={onCLick}>...</div> 
  );
}

function ReportList({ items }) {  
  const handleClick = useCallback((item) => {     
    sendReport(item)   
  }, [item]);   

  return (
    <article>{items.map(item => {       
      return (
        <figure key={item.id}>     
          // такой вызов лишает мемоизацию всякого смысла          
          <Chart onClick={() => handleClick(item)} />
        </figure>      
      );    
    })}   
    </article>
  );
}

useMemo

Позволяет сравнивать зависимости и кэшировать результаты вычислений между ре-рендерами (re-rendering).

Сравнение значений происходит при помощи Object.is()

Причины использовать:

  • Уменьшение количества сложных вычислений
  • Уменьшение количества повторных рендеров компонентов
  • Мемоизация значения, которое используется в зависимостях другого хука

Важно помнить:

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

React.memo (hoc)

Принимает в себя компонент или функцию для ручного сравнения, которая решает обновлять компонент или нет.

Аналоги из классовых компонентов:

  • shoudComponentUpdate
  • PureComponent

Рекомендация

Мемоизацию следует использовать только при наличии реальных проблем с производительностью:

  • Хуки для мемоизации визуально усложняют код
  • Процесс мемоизации затрачивает ресурсы

Задачи:

Дополнительно