JavaRush /Курсы /Модуль 3: React /Оптимизация функций с useCallback и useMemo — когда испол...

Оптимизация функций с useCallback и useMemo — когда использовать

Модуль 3: React
25 уровень , 3 лекция
Открыта

Использование useCallback

Рассмотрим следующий пример. У нас есть родительский компонент, который передаёт функцию-действие дочернему компоненту:

import React, { useState } from 'react';

const ChildComponent = React.memo(({ onClick }: { onClick: () => void }) => {
  console.log('Child re-rendered');
  return <button onClick={onClick}>Click me</button>;
});

const ParentComponent = () => {
  const [count, setCount] = useState(0);

  const increment = () => {
    console.log('Button clicked');
    setCount(count + 1);
  };

  return (
    <div>
      <p>Parent count: {count}</p>
      <ChildComponent onClick={increment} />
    </div>
  );
};

export default ParentComponent;

Кажется, всё работает, но обратите внимание: даже когда состояние count в родительском компоненте изменяется, ChildComponent перерисовывается, хотя он обёрнут в React.memo. Почему? Потому что при каждом рендере создаётся новая версия функции increment, и React считает её изменённой.

Решение с useCallback

Вот как мы можем исправить это:

const ParentComponent = () => {
  const [count, setCount] = useState(0);

  const increment = React.useCallback(() => {
    console.log('Button clicked');
    setCount((prevCount) => prevCount + 1);
  }, []);

  return (
    <div>
      <p>Parent count: {count}</p>
      <ChildComponent onClick={increment} />
    </div>
  );
};

Теперь increment больше не создаётся заново при каждом рендере, и ChildComponent не выполняет лишние перерисовки. Это спасает производительность, особенно в больших компонентах.

Типизация с useCallback

Вот как мы можем типизировать функцию increment:

const increment: () => void = React.useCallback(() => {
  console.log('Button clicked');
  setCount((prevCount) => prevCount + 1);
}, []);

TypeScript автоматически выведет тип функции, но явное указание типа повышает читаемость и защищает код от ошибок.

Управление зависимостями

Не забывайте добавлять зависимости в массив второго аргумента useCallback. Если зависимость пропущена, это может вызвать неожиданные баги.

Использование useMemo

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

const ParentComponent = () => {
  const [count, setCount] = useState(0);
  const [otherState, setOtherState] = useState(false);

  // Эта функция делает тяжёлое вычисление.
  const calculateValue = () => {
    console.log('Calculating...');
    return count * 2;
  };

  const result = calculateValue();

  return (
    <div>
      <p>Result: {result}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
        <button onClick={() => setOtherState(!otherState)}>Toggle</button>
    </div>
  );
};

Каждый раз при переключении otherState вызывает повторный рендер ParentComponent, а вычисление calculateValue снова выполняется, даже если count не изменился.

Решение с useMemo

Мы можем использовать useMemo, чтобы мемоизировать значение:

const ParentComponent = () => {
  const [count, setCount] = useState(0);
  const [otherState, setOtherState] = useState(false);

  const result = React.useMemo(() => {
    console.log('Calculating...');
    return count * 2;
  }, [count]);

  return (
    <div>
      <p>Result: {result}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setOtherState(!otherState)}>Toggle</button>
    </div>
  );
};

Теперь calculateValue вычисляется только тогда, когда значение count изменяется, а не при каждом рендере. Это существенно экономит ресурсы.

Типизация с useMemo

Чтобы типизировать значение, возвращаемое useMemo, можно сделать так:

const result: number = React.useMemo(() => count * 2, [count]);

Если вы забыли типизацию, TypeScript сам выведет тип на основе возвращаемого значения.

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

Используйте:

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

Не злоупотребляйте:

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

Практическое применение

Работа с useCallback и useMemo крайне важна в реальных проектах. Например:

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

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

2
Задача
Модуль 3: React, 25 уровень, 3 лекция
Недоступна
Меморизация функции с useCallback
Меморизация функции с useCallback
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ