← Назад к вопросам

Для чего нужен второй callback в useMemo?

2.3 Middle🔥 211 комментариев
#React#State Management#Оптимизация и производительность

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

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Для чего нужен второй callback в useMemo?

В React хуке useMemo второй параметр — это массив зависимостей (dependency array). Это критически важный механизм для оптимизации и контроля когда мемоизированное значение пересчитывается.

Синтаксис

const memoizedValue = useMemo(
  () => computeExpensiveValue(a, b),  // первый аргумент: функция
  [a, b]                              // второй аргумент: зависимости
);

Как работает массив зависимостей

Re act сравнивает предыдущий массив зависимостей с новым:

const [count, setCount] = useState(0);
const [name, setName] = useState('Alice');

const expensive = useMemo(() => {
  console.log('Computing...');
  return count * 2;
}, [count]);  // Зависит только от count!

// Когда count изменился → пересчитай
// Когда name изменился → НЕ пересчитывай (count же не изменился)

Варианты массива зависимостей

1. Пустой массив [] — считать один раз

const expensiveData = useMemo(() => {
  console.log('Loading data...');
  return fetchData();  // выполнится один раз при монтировании
}, []);

Это полезно для:

  • Инициализации данных
  • Создания объектов, которые никогда не меняются
  • Начальной загрузки API

2. С зависимостями [a, b] — считать если они меняются

const total = useMemo(() => {
  console.log('Computing total...');
  return a + b;
}, [a, b]);

// Если a или b изменилась → пересчитай
// Если другие переменные изменились → не трогай

3. Без второго параметра (ПЛОХО!) — считать на каждый рендер

// ❌ Плохо: это бесполезно, вычисляет каждый раз
const expensive = useMemo(() => {
  return computeExpensive(a);
});

// Такой useMemo ничем не помогает!

Практические примеры

Пример 1: Фильтрация большого списка

const FilteredList = ({ items, filter }) => {
  // Пересчитываем фильтрованный список только если items или filter изменился
  const filteredItems = useMemo(() => {
    console.log('Filtering...');
    return items.filter(item => item.name.includes(filter));
  }, [items, filter]);
  
  return (
    <ul>
      {filteredItems.map(item => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
};

// При рендере родителя, если items и filter не изменились,
// filteredItems останется прежним → ListItem компоненты не перерендерятся

Пример 2: Обработка данных для графика

const DataChart = ({ rawData, metric }) => {
  // Обрабатываем данные только если rawData или metric изменился
  const processedData = useMemo(() => {
    console.time('Processing');
    
    const processed = rawData
      .filter(d => d.metric === metric)
      .map(d => ({ ...d, value: d.value * 1.1 }))
      .sort((a, b) => b.value - a.value);
    
    console.timeEnd('Processing');
    return processed;
  }, [rawData, metric]);
  
  return <BarChart data={processedData} />;
};

Пример 3: Создание объектов для сравнения

const Component = ({ userId, userName }) => {
  // Создаём объект пользователя один раз (пока userId и userName не изменились)
  const user = useMemo(() => {
    return { id: userId, name: userName };
  }, [userId, userName]);
  
  // Можем безопасно передать в другой компонент
  return <UserCard user={user} />;
};

Без useMemo каждый рендер создал бы новый объект:

const user = { id: userId, name: userName };  // ❌ Новый объект каждый раз!
// UserCard будет перерендеряться даже если данные не изменились

Ошибки при использовании зависимостей

Ошибка 1: Забыть добавить зависимость

const value = useMemo(() => {
  return a + b;  // используем a и b
}, [a]);  // ❌ Забыли b!

// Когда b изменится, React не пересчитает значение
// value остаётся старым → баг!

Ошибка 2: Добавить всё подряд

const [a, setA] = useState(0);
const [b, setB] = useState(0);
const [c, setC] = useState(0);

const result = useMemo(() => {
  return a + b;
}, [a, b, c]);  // ❌ c не используется!

// Когда c изменится, React пересчитает a + b (бесполезно)

Советы по использованию

1. Используй ESLint плагин

npm install --save-dev eslint-plugin-react-hooks

Он подскажет какие зависимости забыли:

const value = useMemo(() => {
  return a + b;
}, []);  // ⚠️  ESLint предупредит!

2. Используй useMemo для дорогих вычислений

// Хорошие кейсы для useMemo:
// - Фильтрация большого списка
// - Сортировка (O(n log n))
// - Транформация данных
// - Создание объектов для memo компонентов

// Плохие кейсы:
// - Простые арифметические операции
// - Доступ к свойствам объекта
// - Строковые операции

3. Комбо: useMemo + React.memo

const Parent = () => {
  const data = useMemo(() => {
    return { value: expensiveCalculation() };
  }, []);
  
  return <Child data={data} />;
};

const Child = React.memo(({ data }: Props) => {
  return <div>{data.value}</div>;
});

// Теперь Child не перерендерится, если data не изменился

Производительность

// До useMemo: всё вычисляется каждый раз
const App = () => {
  const [count, setCount] = useState(0);
  const filtered = items.filter(...);  // O(n) на каждый рендер!
  
  return (
    <>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <List items={filtered} />
    </>
  );
};

// После useMemo: вычисляется только при необходимости
const App = () => {
  const [count, setCount] = useState(0);
  const filtered = useMemo(
    () => items.filter(...),
    [items]
  );
  
  return (
    <>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <List items={filtered} />
    </>
  );
};

Резюме

Массив зависимостей — это инструкция React'у:

  • Когда пересчитывать мемоизированное значение
  • Какие переменные влияют на результат
  • Когда результат можно переиспользовать

Правило:

  • Если ничего не меняется → []
  • Если что-то меняется → добавь в [...]
  • Включи в массив все переменные, используемые в функции

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