Для чего используются хуки useMemo и useCallback в React?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Назначение хуков useMemo и useCallback в React
Оба хука — useMemo и useCallback — являются инструментами для оптимизации производительности в React-приложениях. Они помогают избежать лишних вычислений и ререндеров компонентов, сохраняя (мемоизируя) значения между рендерами. Однако их применение и внутренняя механика различаются.
useMemo: Мемоизация вычисляемых значений
useMemo используется для сохранения результата тяжёлых вычислений между рендерами. Он принимает функцию-создатель (create) и массив зависимостей (deps). Хук возвращает мемоизированное значение и пересчитывает его только при изменении одной из зависимостей.
import React, { useMemo } from 'react';
const ExpensiveComponent = ({ items, filterTerm }) => {
// Фильтрация будет выполнена только при изменении items или filterTerm
const filteredItems = useMemo(() => {
console.log('Выполняется дорогая фильтрация...');
return items.filter(item => item.name.includes(filterTerm));
}, [items, filterTerm]); // Зависимости
return (
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
};
Ключевые аспекты useMemo:
- Предотвращение лишних вычислений: Затратные операции (фильтрация больших массивов, сложные математические вычисления, преобразование данных) не будут выполняться на каждом рендере.
- Ссылочная стабильность: Возвращаемое значение сохраняет ссылочную идентичность, если зависимости не изменились. Это особенно важно при передаче значения в качестве пропса в дочерние компоненты, обёрнутые в
React.memo, так как предотвращает их ненужные ререндеры. - Не гарантирует единственный вызов: Это оптимизация, а не гарантия. React может в некоторых случаях "забывать" мемоизированные значения.
useCallback: Мемоизация функций
useCallback предназначен для сохранения ссылки на функцию между рендерами. Он возвращает мемоизированную версию колбэка, которая изменяется только при изменении одной из зависимостей.
import React, { useState, useCallback } from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
const [count, setCount] = useState(0);
// Без useCallback при каждом рендере ParentComponent
// создаётся НОВАЯ функция, что вызовет ререндер ChildComponent
const handleClick = useCallback(() => {
console.log('Клик залогирован, count:', count);
// Важно: здесь count зафиксирован на значении,
// которое было при создании функции (если не указан в deps).
}, []); // Пустой массив зависимостей: функция создаётся единожды
// Более корректный вариант, если нужен актуальный count
const handleClickWithDep = useCallback(() => {
console.log('Актуальный count:', count);
// Какая-то логика с count
}, [count]); // Функция пересоздаётся при изменении count
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Увеличить: {count}</button>
{/* ChildComponent будет ререндериться только если изменится сама функция handleClick */}
<ChildComponent onClick={handleClick} />
</div>
);
};
Ключевые аспекты useCallback:
- Оптимизация дочерних компонентов: Основная цель — передача стабильной ссылки на колбэк в дочерние компоненты, которые используют
React.memoили зависят от сравнения ссылок на пропсы (например, вuseEffectвнутри дочернего компонента). - Зависимости (deps): Крайне важно правильно указывать зависимости. Пустой массив
[]означает, что функция будет создана один раз и будет "видеть" только начальные значения переменных из замыкания. Если внутри функции используется состояние (state) или пропс, их обычно нужно добавлять в массив зависимостей. - Не ускоряет создание функции: Само по себе создание функции — быстрая операция. Проблема, которую решает
useCallback, — не в скорости создания, а в стабильности ссылки, влияющей на ререндеры дочерних компонентов и зависимости эффектов.
Сравнение и общие принципы использования
| Аспект | useMemo | useCallback |
|---|---|---|
| Что мемоизирует | Результат любой функции (значение: объект, массив, примитив) | Саму функцию |
| Синтаксис | useMemo(() => computeValue, deps) | useCallback(fn, deps) |
| Эквивалент | useCallback(() => fn, deps) — это useMemo(() => fn, deps) | useCallback(fn, deps) — это useMemo(() => fn, deps) |
| Основное назначение | Избежать повторных тяжёлых вычислений, обеспечить стабильность ссылки на сложный объект | Обеспечить стабильность ссылки на функцию, передаваемую вниз по дереву компонентов |
Важные предостережения:
- Не использовать повсеместно "на всякий случай". Индексная оптимизация может привести к обратному эффекту: потреблять больше памяти на хранение старых значений и усложнять код. Сначала измеряйте производительность с помощью React DevTools Profiler.
- Проблема "устаревшего замыкания" (stale closure). Неправильно указанные зависимости в
useCallbackмогут привести к использованию устаревших значений переменных внутри функции. - useMemo для создания функций. Для мемоизации функции технически можно использовать и
useMemo:useMemo(() => () => {...}, deps). НоuseCallback— это синтаксический сахар, специально предназначенный для этого случая, и его код читается лучше.
Правило применения: Используйте useMemo для сохранения ресурсоёмких результатов вычислений или сложных объектов, а useCallback — когда необходимо предотвратить ререндер дочернего компонента, который принимает функцию в качестве пропса и обёрнут в React.memo. В остальных случаях часто можно обойтись без этих хуков.