React useMemo

Хук useMemo в React — это хук, который используется для мемоизирования значений.

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

Хук useMemo запускается только тогда, когда обновляются его зависимости.

Это может повысить производительность.

Хуки useMemo и useCallback похожи:

  • useMemo возвращает мемоизированное значение.
  • useCallback возвращает мемоизированную функцию.

Подробнее о useCallback читайте в разделе про хук useCallback.


Синтаксис

Хук useMemo принимает два аргумента:


const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

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

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

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

В данном примере у нас есть тяжелая функция, которая выполняется при каждом рендеринге.

При изменении счётчика или добавлении пункта в список вы заметите задержку в выполнении.

Пример

Ресурсоемкая функция expensiveCalculation запускается при каждом рендеринге:


import { useState } from 'react';
import { createRoot } from 'react-dom/client';

const App = () => {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);
  const calculation = expensiveCalculation(count);

  const increment = () => {
    setCount((c) => c + 1);
  };
  const addTodo = () => {
    setTodos((t) => [...t, "Новый пункт"]);
  };

  return (
    <div>
      <div>
        <h2>Мои дела</h2>
        {todos.map((todo, index) => {
          return <p key={index}>{todo}</p>;
        })}
        <button onClick={addTodo}>Добавить дело</button>
      </div>
      <hr />
      <div>
        Счётчик: {count}
        <button onClick={increment}>+</button>
        <h2>Дорогая функция</h2>
        {calculation}
        <p>Обратите внимание, что эта демонстрация выполняет дорогую функцию также при нажатии на кнопку "Добавить дело".</p>
      </div>
    </div>
  );
};

const expensiveCalculation = (num) => {
  console.log("Вычисляю...");
  for (let i = 0; i < 1000000000; i++) {
    num += 1;
  }
  return num;
};

createRoot(document.getElementById('root')).render(
  <App />
);


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

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

Мы можем обернуть вызов этой функции в useMemo.

Хук useMemo принимает второй параметр для декларации зависимостей. Ресурсоемкая функция будет выполнена только в том случае, если изменились её зависимости.

В следующем примере ресурсоемкая функция будет выполняться только при изменении счётчика count, а не при добавлении пунктов в список.

Пример

Демонстрация производительности с использованием хука useMemo:


import { useState, useMemo } from 'react';
import { createRoot } from 'react-dom/client';

const App = () => {
  const [count, setCount] = useState(0);
  const [todos, setTodos] = useState([]);
  const calculation = useMemo(() => expensiveCalculation(count), [count]);

  const increment = () => {
    setCount((c) => c + 1);
  };
  const addTodo = () => {
    setTodos((t) => [...t, "Новый пункт"]);
  };

  return (
    <div>
      <div>
        <h2>Мои дела</h2>
        {todos.map((todo, index) => {
          return <p key={index}>{todo}</p>;
        })}
        <button onClick={addTodo}>Добавить дело</button>
      </div>
      <hr />
      <div>
        Счётчик: {count}
        <button onClick={increment}>+</button>
        <h2>Дорогая функция</h2>
        {calculation}
      </div>
    </div>
  );
};

const expensiveCalculation = (num) => {
  console.log("Вычисляю...");
  for (let i = 0; i < 1000000000; i++) {
    num += 1;
  }
  return num;
};

createRoot(document.getElementById('root')).render(
  <App />
);


Зачем используется useMemo

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

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

    Пример:

    
    const filteredItems = useMemo(() => {
      return items.filter(item => item.isActive);
    }, [items]);
    
    
  2. Предотвращение ненужных ререндеров дочерних компонентов

    При передаче объектов или массивов в дочерние компоненты React сравнивает их по ссылке. Даже если содержимое одинаковое, при каждом рендере создаётся новый объект — это вызывает ререндер. useMemo сохраняет ссылку на значение, пока зависимости не изменились.

    Пример:

    
    const styleProps = useMemo(() => ({ color: 'blue', fontSize: 16 }), []);
    return <Button style={styleProps} />;
    
    
  3. Использование в связке с useCallback и useEffect

    Значения, вычисленные с помощью useMemo, могут использоваться как зависимости в useEffect или useCallback, чтобы избежать лишних вызовов.


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

  • При дорогостоящих вычислениях (например, map, filter, reduce на больших массивах).
  • При передаче массивов/объектов в оптимизированные компоненты (React.memo).
  • Когда значение используется как зависимость в других хуках.

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

  • Для простых вычислений (например, x + y) — накладные расходы на мемоизирование превысят выгоду.
  • Если зависимости меняются на каждом рендере — кэш будет сбрасываться каждый раз, и смысла в useMemo нет.
  • Если это ухудшает читаемость кода без реального выигрыша в производительности.

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

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

Таким образом, useMemo — мощный инструмент для оптимизации, но его стоит применять осознанно, только там, где это действительно необходимо.