Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI21 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Почему нельзя везде писать useCallback
Это очень частая ошибка начинающих разработчиков — использовать useCallback везде в попытке оптимизировать производительность. На самом деле это создаёт больше проблем, чем решает.
Краткий ответ
useCallback добавляет оверхед (память, сложность, затраты на сравнение зависимостей), который часто больше, чем выигрыш от оптимизации. Используй его только когда это действительно нужно.
Проблема 1: Оверхед памяти
// ❌ ПЛОХО: Каждый useCallback занимает память
function Component() {
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
const handleHover = useCallback(() => {
console.log('hovered');
}, []);
const handleFocus = useCallback(() => {
console.log('focused');
}, []);
const handleBlur = useCallback(() => {
console.log('blurred');
}, []);
const handleChange = useCallback(() => {
console.log('changed');
}, []);
// Все эти useCallback занимают память
return (
<input
onClick={handleClick}
onHover={handleHover}
onFocus={handleFocus}
onBlur={handleBlur}
onChange={handleChange}
/>
);
}
// ✅ ХОРОШО: Только где действительно нужно
function Component() {
return (
<input
onClick={() => console.log('clicked')}
onHover={() => console.log('hovered')}
onFocus={() => console.log('focused')}
onBlur={() => console.log('blurred')}
onChange={() => console.log('changed')}
/>
);
}
Проблема 2: Оверхед обработки зависимостей
Каждый раз React должен сравнивать массив зависимостей:
function Component({ itemId, userId, time }) {
// ❌ ПЛОХО: React должен проверять 3 зависимости
const handleUpdate = useCallback(() => {
updateItem(itemId, userId, time);
}, [itemId, userId, time]); // Три сравнения каждый рендер
return <Button onClick={handleUpdate}>Update</Button>;
}
// ✅ ХОРОШО: Прямая функция
function Component({ itemId, userId, time }) {
return (
<Button
onClick={() => updateItem(itemId, userId, time)}
>
Update
</Button>
);
}
Проблема 3: Усложнение кода
// ❌ ПЛОХО: Кода больше, сложнее читать
function Component() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(c => c + 1);
}, []);
const decrement = useCallback(() => {
setCount(c => c - 1);
}, []);
const reset = useCallback(() => {
setCount(0);
}, []);
return (
<div>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Reset</button>
</div>
);
}
// ✅ ХОРОШО: Простой и понятный код
function Component() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>+</button>
<button onClick={() => setCount(c => c - 1)}>-</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
Проблема 4: Ошибки в зависимостях
// ❌ ПЛОХО: Забыли зависимость
function Component({ userId }) {
const handleFetch = useCallback(() => {
// Используем userId, но его нет в зависимостях!
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => console.log(data));
}, []); // ⚠️ userId не в зависимостях
// handleFetch будет использовать старое значение userId
}
// ✅ ХОРОШО: Правильные зависимости
function Component({ userId }) {
const handleFetch = useCallback(() => {
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => console.log(data));
}, [userId]); // userId включена в зависимости
}
// Но если зависимостей много, может быть лучше без useCallback
function ComponentSimple({ userId }) {
// Прямая функция без проблем с зависимостями
const handleFetch = () => {
fetch(`/api/users/${userId}`)
.then(r => r.json())
.then(data => console.log(data));
};
}
Проблема 5: Сложность отладки
// ❌ ПЛОХО: Сложно отлаживать замыкания
function Component() {
const state1 = useState(0)[0];
const state2 = useState(0)[0];
const state3 = useState(0)[0];
const complexCallback = useCallback(() => {
// Какое значение state1 будет использоваться?
// Старое из замыкания или новое?
// Нужно знать, когда callback был создан
console.log(state1, state2, state3);
}, [state1, state2, state3]); // Много зависимостей
}
// ✅ ХОРОШО: Простая функция, нет замыканий
function Component() {
const state1 = useState(0)[0];
const state2 = useState(0)[0];
const state3 = useState(0)[0];
// Очевидно какие значения используются
const callback = () => {
console.log(state1, state2, state3);
};
}
Когда useCallback действительно нужен
1. Передача в React.memo дочерний компонент
// ✅ ПРАВИЛЬНО: Дочерний компонент часто перерендеряется
const ExpensiveChild = React.memo(({ onCallback }) => {
console.log('Child rendered');
return <div>Child</div>;
});
function Parent() {
// Без useCallback, Child перерендеряется на каждый рендер Parent
const handleClick = useCallback(() => {
console.log('clicked');
}, []);
return <ExpensiveChild onCallback={handleClick} />;
}
2. Функция в зависимостях useEffect
// ✅ ПРАВИЛЬНО: Функция используется в других эффектах
function Component() {
const fetchData = useCallback(async () => {
const response = await fetch('/api/data');
return response.json();
}, []);
useEffect(() => {
// fetchData в зависимостях
fetchData().then(data => console.log(data));
}, [fetchData]); // Если функция не стабильна, эффект срабатывает часто
}
3. Передача функции в библиотеку
// ✅ ПРАВИЛЬНО: Библиотека сохраняет функцию
function Component() {
const handleChange = useCallback((value) => {
// Обновляем состояние
}, []);
useEffect(() => {
// Библиотека подписывается на изменения
// Если функция пересоздаётся, подписка ломается
const subscription = externalLibrary.onChange(handleChange);
return () => subscription.unsubscribe();
}, [handleChange]);
}
Правило большого пальца
// Используй useCallback только если:
// 1. Функция передаётся в React.memo компонент
// 2. Функция используется в зависимостях другого эффекта
// 3. Функция передаётся в внешнюю библиотеку
// 4. Профайлер показал, что это узкое место
// В остальных случаях просто используй обычную функцию
Реальный пример неправильного использования
// ❌ ПЛОХО: useCallback везде
function UserCard({ user, onUpdate }) {
const handleEdit = useCallback(() => {
onUpdate(user);
}, [user, onUpdate]);
const handleDelete = useCallback(() => {
deleteUser(user.id);
}, [user]);
const handleArchive = useCallback(() => {
archiveUser(user.id);
}, [user]);
return (
<div>
<button onClick={handleEdit}>Edit</button>
<button onClick={handleDelete}>Delete</button>
<button onClick={handleArchive}>Archive</button>
</div>
);
}
// ✅ ХОРОШО: Простые функции
function UserCard({ user, onUpdate }) {
return (
<div>
<button onClick={() => onUpdate(user)}>Edit</button>
<button onClick={() => deleteUser(user.id)}>Delete</button>
<button onClick={() => archiveUser(user.id)}>Archive</button>
</div>
);
}
Метрика: Когда это имеет значение
Используй useCallback, только если выполнены условия:
- Компонент перерендеряется более 10 раз в секунду (редко!)
- Дочерний компонент обёрнут в React.memo (есть смысл оптимизировать)
- Профайлер React показал проблему (нужны доказательства)
Без этого — просто используй обычные функции.
Вывод
- useCallback это оптимизация для специфических случаев
- Код без useCallback простой и понятный — используй когда можешь
- Оверхед от useCallback часто больше выигрыша — профайли перед оптимизацией
- Думай о производительности последним — сначала пиши простой код
- "Make it work, make it right, make it fast" — в этом порядке