← Назад к вопросам
Можно ли закэшировать функцию с помощью useMemo?
2.0 Middle🔥 171 комментариев
#React
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Кэширование функции с помощью useMemo
Это очень важный вопрос, который проверяет глубокое понимание React хуков. Краткий ответ: технически можно, но это НЕ правильный подход. Давайте разберёмся подробно.
Краткий ответ
Да, технически useMemo может вернуть функцию и кэшировать её. Но это неправильно. Для кэширования функций используй useCallback.
Почему можно использовать useMemo
усeMemo кэширует любое значение, включая функции:
function Example() {
// useMemo может вернуть функцию
const memoizedFunction = useMemo(() => {
return function handleClick() {
console.log('clicked');
};
}, []);
return <button onClick={memoizedFunction}>Click</button>;
}
Технически это работает, но это плохая практика.
Почему это НЕ правильно
1. Нечитаемость и путанность
useMemo предназначен для дорогих вычислений, а не функций:
// ❌ Плохо - путающий код
const memoizedFn = useMemo(() => {
return () => console.log('clicked');
}, []);
// ✅ Хорошо - ясный код
const memoizedFn = useCallback(() => {
console.log('clicked');
}, []);
2. useCallback - это специально для функций
useCallback — это специальный хук, созданный именно для кэширования функций:
// useCallback это буквально:
// const useCallback = (fn, deps) => useMemo(() => fn, deps);
// Вот реальная реализация useCallback:
function useCallback(callback, dependencies) {
return useMemo(() => callback, dependencies);
}
3. Семантика кода
Люди, читающие ваш код, ожидают useMemo для вычислений, а useCallback для функций:
// Когда видит useMemo, думает: "Здесь дорогие вычисления"
const result = useMemo(() => computeHeavyValue(), [deps]);
// Когда видит useCallback, думает: "Здесь функция для передачи в дочерний компонент"
const handleClick = useCallback(() => { }, [deps]);
Правильный способ - useCallback
function Component({ onUpdate }) {
// ✅ Правильно
const handleClick = useCallback(() => {
onUpdate({ id: 1, name: 'John' });
}, [onUpdate]);
return <Button onClick={handleClick}>Update</Button>;
}
Практические примеры
Пример 1: Передача функции в дочерний компонент
function Parent() {
const [count, setCount] = React.useState(0);
// ❌ ПЛОХО: функция пересоздаётся на каждый рендер
function handleClick() {
setCount(count + 1);
}
return (
<div>
<Child onButtonClick={handleClick} />
</div>
);
}
const Child = React.memo(({ onButtonClick }) => {
console.log('Child rendered');
return <button onClick={onButtonClick}>Click</button>;
});
// Проблема: Child будет перерендеряться на каждый рендер Parent
// потому что handleClick это новая функция каждый раз
function ParentFixed() {
const [count, setCount] = React.useState(0);
// ✅ ХОРОШО: useCallback кэширует функцию
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
return (
<div>
<Child onButtonClick={handleClick} />
</div>
);
}
const Child = React.memo(({ onButtonClick }) => {
console.log('Child rendered'); // Теперь не перерендеряется
return <button onClick={onButtonClick}>Click</button>;
});
Пример 2: Функция как зависимость других эффектов
function DataFetcher({ searchTerm, onDataFetch }) {
// ❌ ПЛОХО: useEffect будет срабатывать часто
React.useEffect(() => {
const fetchData = async () => {
const response = await fetch(`/api/search?q=${searchTerm}`);
const data = await response.json();
onDataFetch(data);
};
fetchData();
}, [searchTerm, onDataFetch]); // onDataFetch пересоздаётся часто
}
function ParentBad() {
const [results, setResults] = React.useState([]);
// Эта функция пересоздаётся на каждый рендер
const handleDataFetch = (data) => {
setResults(data);
};
return <DataFetcher searchTerm="react" onDataFetch={handleDataFetch} />;
}
// ✅ ХОРОШО: useCallback стабилизирует функцию
function ParentFixed() {
const [results, setResults] = React.useState([]);
const handleDataFetch = useCallback((data) => {
setResults(data);
}, []);
return <DataFetcher searchTerm="react" onDataFetch={handleDataFetch} />;
}
Пример 3: Использование функции в зависимостях
function Component() {
const [data, setData] = React.useState(null);
// ❌ ПЛОХО: filter функция создаётся каждый рендер
// useEffect будет срабатывать постоянно
React.useEffect(() => {
const filter = (items) => items.filter(item => item.active);
const filtered = filter(data);
console.log(filtered);
}, [data, filter]); // filter пересоздаётся каждый рендер
}
function ComponentFixed() {
const [data, setData] = React.useState(null);
// ✅ ХОРОШО: filter кэшируется
const filter = useCallback(
(items) => items.filter(item => item.active),
[]
);
React.useEffect(() => {
const filtered = filter(data);
console.log(filtered);
}, [data, filter]); // filter стабилен
}
Когда useCallback критичен
1. Передача в React.memo компонент
const ExpensiveChild = React.memo(({ onCallback }) => {
return <div>Child</div>;
});
function Parent() {
// Без useCallback, ExpensiveChild перерендеряется
const onCallback = useCallback(() => {}, []);
return <ExpensiveChild onCallback={onCallback} />;
}
2. Зависимость в useEffect
function Component() {
const handleChange = useCallback(() => {
// ...
}, []);
useEffect(() => {
// subscribe использует handleChange
subscribe(handleChange);
}, [handleChange]); // handleChange стабилен
}
3. Передача в событий для библиотек
function Component() {
const handleChange = useCallback((value) => {
// ...
}, []);
React.useEffect(() => {
editor.on('change', handleChange);
return () => editor.off('change', handleChange);
}, [handleChange]);
}
Сравнение useMemo и useCallback
// useMemo кэширует РЕЗУЛЬТАТ вычисления
const expensiveValue = useMemo(() => {
return computeExpensiveValue(a, b);
}, [a, b]);
// useCallback кэширует ФУНКЦИЮ
const callback = useCallback(() => {
doSomething(a, b);
}, [a, b]);
// Эквивалентно:
const callback = useMemo(() => {
return () => {
doSomething(a, b);
};
}, [a, b]);
// Но useCallback более читаемо и явно для функций
Производительность
// useMemo на функции имеет небольшой оверхед
// useCallback специально оптимизирован для функций
// Это микрооптимизация, но useCallback немного быстрее
const memoized = useMemo(() => fn, deps); // Немного медленнее
const callback = useCallback(fn, deps); // Немного быстрее
Вывод
- Технически можно использовать useMemo для кэширования функций
- На практике всегда используй useCallback для функций
- useCallback — это правильный инструмент, созданный специально для этого
- Читаемость кода важнее, чем микрооптимизация
- Семантика кода помогает другим разработчикам понять ваши намерения
Используй правильный инструмент для правильной задачи!