useMemo и useCallback в React — оптимизация производительности

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

Коротко: разница

ХукЧто запоминает
useMemoРезультат вычисления
useCallbackФункцию
const filteredItems = useMemo(() => {
  return items.filter(item => item.active);
}, [items]);

const handleSelect = useCallback((id) => {
  setSelectedId(id);
}, []);

Когда нужен useMemo

useMemo полезен, если вычисление реально дорогое или результат передаётся в memo-компонент, где важна стабильная ссылка. Простые операции вроде count + 1 мемоизировать не нужно.

function ProductList({ products, query }) {
  const visibleProducts = useMemo(() => {
    return products.filter(product =>
      product.title.toLowerCase().includes(query.toLowerCase())
    );
  }, [products, query]);

  return visibleProducts.map(product => (
    <ProductCard key={product.id} product={product} />
  ));
}

Когда нужен useCallback

useCallback нужен, когда функция передаётся в дочерний компонент, обёрнутый в React.memo, или используется в зависимостях другого hook.

const ProductCard = memo(function ProductCard({ product, onBuy }) {
  return (
    <article>
      <h3>{product.title}</h3>
      <button onClick={() => onBuy(product.id)}>Купить</button>
    </article>
  );
});

function Catalog({ products }) {
  const handleBuy = useCallback((id) => {
    console.log('buy', id);
  }, []);

  return products.map(product => (
    <ProductCard key={product.id} product={product} onBuy={handleBuy} />
  ));
}

Ошибка новичков

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

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

Практическое правило

  • Есть тяжёлое вычисление на большом массиве — подумайте о useMemo.
  • Функция ломает memo-дочерний компонент — подумайте о useCallback.
  • Компонент простой и быстрый — не усложняйте код.
  • Если зависимости постоянно меняются, мемоизация почти ничего не даст.

Пример: сортировка и фильтрация

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

function Products({ products, query, sort }) {
  const visibleProducts = useMemo(() => {
    return products
      .filter(product => product.title.toLowerCase().includes(query.toLowerCase()))
      .sort((a, b) => {
        if (sort === 'price') return a.price - b.price;
        return a.title.localeCompare(b.title);
      });
  }, [products, query, sort]);

  return visibleProducts.map(product => (
    <ProductCard key={product.id} product={product} />
  ));
}

Как понять, что оптимизация нужна

  • Компонент заметно тормозит при вводе или клике.
  • Внутри есть фильтрация, сортировка, группировка большого массива.
  • Дочерние компоненты обёрнуты в React.memo и зависят от стабильных props.
  • Профилировщик React показывает лишние рендеры.

Плохой сигнал

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

Как не переборщить с оптимизацией

useMemo и useCallback часто добавляют слишком рано. От этого код становится сложнее, а приложение не становится быстрее. Эти хуки стоит применять там, где есть понятная причина.

Хорошая оптимизация оставляет понятный код. Если ради useMemo приходится долго объяснять зависимости и побочные эффекты, а пользователь не видит разницы, лучше вернуться к простой версии и оптимизировать только подтверждённые узкие места.

Когда причина есть

  • фильтруется или сортируется большой массив;
  • дочерний компонент обёрнут в React.memo;
  • функция используется в зависимостях другого hook;
  • React Profiler показывает лишние тяжёлые рендеры;
  • пользователь реально видит задержку при вводе или клике.

Когда лучше удалить

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

Если сомневаетесь

Сначала напишите обычную версию. Потом создайте нагрузку: большой список, частый ввод, тяжёлый дочерний компонент. Только после этого добавляйте useMemo или useCallback и проверяйте, стало ли лучше.

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x