Что нужно useCallback для избежания лишних рендеров?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что нужно useCallback для избежания лишних рендеров?
useCallback нужен для мемоизации функций и предотвращения ненужных рендеров дочерних компонентов, которые получают эту функцию как prop. Это работает вместе с React.memo().
Проблема: пересоздание функций при каждом рендере
Без useCallback (проблема):
function Parent() {
const [count, setCount] = useState(0);
// Эта функция создаётся ЗАНОВО при каждом рендере Parent
const handleClick = () => {
console.log('Клик!');
};
return <Child onClick={handleClick} />;
}
function Child({ onClick }) {
console.log('Child отрендерился');
return <button onClick={onClick}>Клик</button>;
}
export default React.memo(Child); // React.memo не спасает - функция разная каждый раз!
При каждом рендере Parent функция handleClick создаётся заново. Даже если Child завёрнут в React.memo(), он всё равно перерендерится, потому что prop onClick — это новая функция (новый объект).
Решение: useCallback
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// Функция создаётся один раз и переиспользуется
const handleClick = useCallback(() => {
console.log('Клик!');
}, []); // Пустой массив зависимостей = никогда не обновляется
return <Child onClick={handleClick} />;
}
function Child({ onClick }) {
console.log('Child отрендерился (только один раз)');
return <button onClick={onClick}>Клик</button>;
}
export default React.memo(Child);
Теперь handleClick — это одна и та же функция, и React.memo() видит, что prop не изменился, поэтому Child не перерендерится.
Зависимости useCallback
Без зависимостей (функция никогда не обновляется):
const handleClick = useCallback(() => {
console.log('Клик!');
}, []); // Функция всегда одна и та же
С зависимостями (функция обновляется, если они меняются):
function Parent({ userId }) {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log(`Пользователь ${userId} кликнул`);
}, [userId]); // Функция пересоздаётся, если userId меняется
return <Child onClick={handleClick} />;
}
Если userId изменится, функция пересоздаётся, и Child перерендерится (это нужно, потому что логика изменилась).
Реальный пример: управление списком
function TodoList() {
const [todos, setTodos] = useState([]);
// БЕЗ useCallback: новая функция каждый раз = перерендер всех TodoItem
// const addTodo = () => { ... };
// С useCallback: одна функция = NO перерендеров TodoItem
const addTodo = useCallback((text) => {
setTodos((prev) => [...prev, { id: Date.now(), text }]);
}, []);
const deleteTodo = useCallback((id) => {
setTodos((prev) => prev.filter((t) => t.id !== id));
}, []);
return (
<>
<button onClick={() => addTodo('Новое')}>Добавить</button>
<ul>
{todos.map((todo) => (
<TodoItem
key={todo.id}
todo={todo}
onDelete={deleteTodo}
/>
))}
</ul>
</>
);
}
function TodoItem({ todo, onDelete }) {
console.log(`TodoItem ${todo.id} отрендерился`);
return (
<li>
{todo.text}
<button onClick={() => onDelete(todo.id)}>Удалить</button>
</li>
);
}
export default React.memo(TodoItem);
Когда использовать useCallback
Используй useCallback:
- Функция передаётся как prop в React.memo() компонент
- Функция передаётся в массив зависимостей другого хука (useEffect, useMemo)
- Функция дорогостоящая (редко, но может быть)
НЕ используй useCallback:
- Функция не передаётся как prop
- Нет React.memo() на дочерних компонентах
- Производительность критична (помни: сам useCallback имеет оверхед)
Частая ошибка: забытые зависимости
// ОШИБКА: функция закрывает userId, но забыли зависимость
const handleClick = useCallback(() => {
console.log(userId); // userId устарел!
}, []); // Должен быть [userId]
// ПРАВИЛЬНО:
const handleClick = useCallback(() => {
console.log(userId);
}, [userId]);
Резюме
- useCallback мемоизирует функцию — она не пересоздаётся при каждом рендере
- Работает с React.memo() — предотвращает ненужные рендеры дочерних компонентов
- Требует правильных зависимостей — иначе функция будет устарелой
- Не панацея — используй только когда это действительно нужно
Это мощный инструмент для оптимизации производительности, особенно в больших приложениях с множеством компонентов.