Зачем использовал хук useCallBack в React?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Зачем использовать useCallback в React
useCallback — это хук React, который мемоизирует функцию и возвращает стабильную ссылку на неё. Это критически важно для оптимизации производительности в определённых сценариях.
Проблема: неправильные ссылки на функции
При каждом рендере функциональный компонент создаёт новые функции, даже если их код не изменился:
function ParentComponent() {
const [count, setCount] = useState(0);
// Эта функция создаётся заново при каждом рендере!
const handleClick = () => {
console.log("Нажата кнопка");
};
return <ChildComponent onClick={handleClick} />;
}
Проблема в том, что если ChildComponent обёрнут в React.memo (оптимизация для пропускания рендера при неизменённых props), то новая функция handleClick будет считаться "другим" объектом, и memo не сработает.
function ChildComponent({ onClick }) {
console.log("ChildComponent перерисовался");
return <button onClick={onClick}>Кнопка</button>;
}
// С React.memo дочерний компонент пересчитывается,
// хотя onClick логически остался тем же
export default React.memo(ChildComponent);
Решение: useCallback
useCallback создаёт стабильную ссылку на функцию и кэширует её, пока не изменятся зависимости:
function ParentComponent() {
const [count, setCount] = useState(0);
// handleClick останется одной и той же функцией,
// пока count не изменится
const handleClick = useCallback(() => {
console.log("Счётчик:", count);
}, [count]); // Зависимость
return <ChildComponent onClick={handleClick} />;
}
Теперь при рендере ParentComponent, если count не изменился, handleClick будет одной и той же функцией, и React.memo не пересчитает ChildComponent.
Массив зависимостей
Это ключевой момент в useCallback. Функция будет пересоздана только если зависимости изменились:
const handleSubmit = useCallback(
(formData) => {
api.post("/submit", { userId, formData });
},
[userId] // Пересоздаём только если userId изменился
);
Если забыть указать зависимость, функция может работать с устаревшими значениями (stale closure):
// ❌ ОШИБКА: userId может быть устаревшим
const handleClick = useCallback(() => {
console.log("userId:", userId);
}, []); // Пустые зависимости!
// ✅ ПРАВИЛЬНО
const handleClick = useCallback(() => {
console.log("userId:", userId);
}, [userId]);
Практический пример: фильтр списка
function FilteredList() {
const [filter, setFilter] = useState("");
const [items, setItems] = useState([]);
// При каждом рендере создаётся новая функция
// Это может быть дорого, если она используется в дочерних компонентах
const handleFilter = useCallback((searchTerm) => {
setFilter(searchTerm);
// Загрузка отфильтрованных данных
fetch(`/api/items?q=${searchTerm}`)
.then(res => res.json())
.then(setItems);
}, []); // Зависимостей нет, функция стабильна
const filteredItems = items.filter(item =>
item.name.toLowerCase().includes(filter.toLowerCase())
);
return (
<div>
<SearchInput onFilter={handleFilter} />
<ItemList items={filteredItems} />
</div>
);
}
function SearchInput({ onFilter }) {
return (
<input
onChange={(e) => onFilter(e.target.value)}
placeholder="Поиск..."
/>
);
}
const ItemList = React.memo(({ items }) => {
// Здесь мемоизация сработает правильно,
// т.к. onFilter (handleFilter) остаётся стабильной
return <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
});
Когда НЕ использовать useCallback
useCallback вводит свои затраты — функция всё равно создаётся и хранится в памяти. Используй его только когда есть явная необходимость:
- Передача функции в memo-компонент — это основной use case
- Функция в зависимостях другого эффекта — чтобы избежать бесконечных циклов
- Функция вызывается в списке элементов — дорого пересчитывать каждый элемент
// ❌ Ненужный useCallback
const handleChange = useCallback((e) => {
setState(e.target.value);
}, []); // Нет memo, просто так
// ✅ Полезный useCallback
const memoChild = React.memo(({ onChange }) => {
// Компонент не будет пересчитываться, если onChange стабильна
return <input onChange={onChange} />;
});
function Parent() {
const handleChange = useCallback((e) => {
setState(e.target.value);
}, []);
return <memoChild onChange={handleChange} />;
}
Сравнение с useMemo
- useCallback(fn, deps) — кэширует саму функцию
- useMemo(fn, deps) — кэширует результат выполнения функции
// useCallback: кэшируем функцию
const memoizedCallback = useCallback(
() => doSomething(a, b),
[a, b]
);
// useMemo: кэшируем результат функции
const memoizedResult = useMemo(
() => doSomething(a, b),
[a, b]
);
Правило большого пальца
Не используй useCallback "на всякий случай" — это усложнит код без пользы. Используй профилировщик React DevTools, чтобы выявить реальные проблемы производительности, а затем применяй оптимизацию. В большинстве приложений useCallback нужен довольно редко.