Что такое мемоизация и зачем использовать useMemo/useCallback?
Мемоизация — это техника оптимизации, при которой результат выполнения функции кэшируется на основе её входных параметров, чтобы при повторном вызове с теми же аргументами не выполнять вычисления заново, а сразу вернуть сохранённое значение. В React мемоизация часто используется для оптимизации производительности компонентов, предотвращения лишних перерендеров и избежания создания новых объектов или функций на каждом рендере.
В React для мемоизации существуют два основных хука: useMemo и useCallback. Оба используются в функциональных компонентах и помогают контролировать, когда необходимо пересоздавать значения или функции.
Что делает useMemo
Хук useMemo позволяет мемоизировать результат вычисления функции. Это означает, что функция будет вызвана только тогда, когда значения из массива зависимостей изменились.
Синтаксис:
const memoizedValue = useMemo(() => computeValue(a, b), [a, b]);
-
computeValue(a, b) — функция, результат которой нужно сохранить
-
[a, b] — массив зависимостей. Если ни одно значение не изменилось, useMemo вернёт сохранённое значение, не вызывая функцию
Когда использовать:
-
Вычисление значения ресурсоёмкое или дорогостоящее (например, фильтрация большого массива, сортировка, форматирование данных)
-
Вы не хотите пересоздавать объект/массив, если его значения не изменились
-
Вы хотите предотвратить нежелательные перерендеры в дочерних компонентах
Пример:
const filteredList = useMemo(() => {
return items.filter(item => item.visible);
}, \[items\]);
Без useMemo, при каждом рендере filteredList будет создаваться заново, даже если items не изменились.
Что делает useCallback
Хук useCallback мемоизирует функцию, а не значение. Он возвращает ту же функцию между рендерами, если зависимости не изменились. Это особенно важно при передаче функций в дочерние компоненты, обёрнутые в React.memo.
Синтаксис:
const memoizedFn = useCallback(() => {
doSomething(a);
}, \[a\]);
-
() => { doSomething(a); } — функция, которую вы хотите мемоизировать
-
[a] — зависимости, при изменении которых функция будет пересоздана
Когда использовать:
-
Вы передаёте функцию в компонент, который сравнивает props с помощью React.memo или shouldComponentUpdate
-
Вы хотите избежать создания новой функции при каждом рендере
-
Используете функцию в useEffect, чтобы она не считалась новой на каждом рендере
Пример:
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, \[\]);
Разница между useMemo и useCallback
- useMemo → возвращает **значение
** - useCallback → возвращает **функцию
** - useCallback(fn, deps) эквивалентен useMemo(() => fn, deps)
Обычно:
-
если вы мемоизируете функцию → useCallback
-
если вы мемоизируете данные/результаты вычислений → useMemo
Проблема с перерендером и как мемоизация помогает
Когда React перерендеривает компонент:
-
все функции, созданные в теле компонента, будут пересозданы
-
все объекты/массивы, созданные в теле компонента, будут считаться новыми
Это может привести к:
-
повторной перерисовке дочерних компонентов
-
срабатыванию useEffect без причины
-
ухудшению производительности
Используя useMemo и useCallback, вы создаёте стабильные ссылки на значения и функции, что помогает избежать лишней работы.
Пример без useCallback:
const onPress = () => {
console.log('pressed');
};
<MyButton onPress={onPress} />
Каждый рендер создаёт новую функцию → компонент MyButton, обёрнутый в React.memo, будет перерендериваться каждый раз.
С использованием useCallback:
const onPress = useCallback(() => {
console.log('pressed');
}, \[\]);
Теперь onPress сохраняет ссылку между рендерами, если зависимости не изменяются.
Потенциальные ловушки
-
Избыточная мемоизация может навредить: иногда она создаёт больше накладных расходов, чем пользы, особенно если операция не ресурсоёмкая.
-
Неверные зависимости в массиве могут привести к неправильному кэшированию и багам. Всегда указывайте все используемые внутри зависимости.
-
useMemo не делает глубокое сравнение — если зависимостью является объект или массив, и вы пересоздаёте его каждый раз, useMemo не поможет.
Меморизация с React.memo
Мемоизация значений или функций имеет смысл только в сочетании с мемоизацией компонентов. React.memo оборачивает компонент и предотвращает его ререндер, если props не изменились (по поверхностному сравнению).
Пример:
const MyComponent = React.memo(({ onClick }) => {
console.log('render');
return <Button onPress={onClick} title="Нажми" />;
});
Если onClick — новая функция на каждом рендере, MyComponent будет перерендериваться. Используйте useCallback.
useMemo для мемоизации объектов
Без useMemo:
const style = { color: 'red' };
Каждый раз создаётся новый объект → useEffect, следящий за style, будет срабатывать. С useMemo:
const style = useMemo(() => ({ color: 'red' }), \[\]);
Теперь объект сохраняется, пока зависимости не изменятся.
Мемоизация с помощью useMemo и useCallback — это мощный инструмент для повышения производительности и контроля ререндеров в React. Но применять её стоит осознанно, основываясь на профилировании и анализе поведения компонентов.