В каких случаях нужна мемоизация callback
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Мемоизация callback функций в React
useCallback — это хук для мемоизации функций-обработчиков. Он сохраняет функцию в памяти между рендерами, если зависимости не изменились. Разберем, когда это действительно нужно.
Основная проблема без мемоизации
В React каждый рендер компонента создает новые функции:
function Parent() {
// Эта функция создается заново при каждом рендере
const handleClick = () => console.log('Clicked!');
return <Child onClick={handleClick} />;
}
function Child({ onClick }) {
// Каждый раз onClick имеет новую ссылку,
// Child будет перерендериться даже если логика не изменилась
return <button onClick={onClick}>Click me</button>;
}
Когда нужен useCallback
1. Передача callback в зависимостях эффектов
Это самый важный случай. Когда callback включен в зависимости useEffect, он должен быть стабильным:
function SearchComponent() {
const [query, setQuery] = useState('');
// БЕЗ useCallback — эффект запускается на каждый рендер
const fetchResults = () => {
fetch(`/api/search?q=${query}`);
};
useEffect(() => {
fetchResults(); // fetchResults меняется каждый рендер!
}, [fetchResults]); // Бесконечный цикл
return <input onChange={(e) => setQuery(e.target.value)} />;
}
// ПРАВИЛЬНО:
function SearchComponent() {
const [query, setQuery] = useState('');
const fetchResults = useCallback(() => {
fetch(`/api/search?q=${query}`);
}, [query]); // Функция меняется только если query меняется
useEffect(() => {
fetchResults();
}, [fetchResults]); // Теперь это безопасно
return <input onChange={(e) => setQuery(e.target.value)} />;
}
2. Оптимизация перерендеров дочерних компонентов
Когда дочерний компонент оптимизирован через React.memo(), стабильная функция предотвращает ненужные перерендеры:
const Child = React.memo(({ onClick }) => {
console.log('Child rendered');
return <button onClick={onClick}>Click</button>;
});
function Parent() {
// БЕЗ useCallback — Child перерендеривается при каждом рендере Parent
const handleClick = () => console.log('Clicked');
return <Child onClick={handleClick} />;
}
// ПРАВИЛЬНО:
function Parent() {
const handleClick = useCallback(() => {
console.log('Clicked');
}, []); // Функция не меняется
return <Child onClick={handleClick} />; // Child не перерендеривается
}
3. Функции как зависимости других хуков
Если callback нужен в useMemo или других хуках:
function FormComponent() {
const [formData, setFormData] = useState({ name: '', email: '' });
// Стабильный колбэк для валидации
const validateForm = useCallback(() => {
return formData.name.length > 0 && formData.email.includes('@');
}, [formData]);
// Используем его в другом хуке
const isValid = useMemo(() => {
return validateForm();
}, [validateForm]);
return <div>Valid: {isValid ? 'Yes' : 'No'}</div>;
}
Когда мемоизация НЕ нужна
1. Простая передача props без оптимизации
Если дочерний компонент не мемоизирован, useCallback просто тратит ресурсы:
// Ненужно:
function Parent() {
const handleClick = useCallback(() => {
// ...
}, []);
return <div onClick={handleClick}>Click</div>; // div не мемоизирован
}
2. Callback не в зависимостях эффектов
Если функция не используется в useEffect или других хуках, мемоизация не помогает:
// Можно без useCallback:
function Button() {
const handleClick = () => console.log('Clicked');
return <button onClick={handleClick}>Click</button>;
}
Правило большого пальца
Используй useCallback если:
- Callback передается в зависимости
useEffectили другого хука - Дочерний компонент оптимизирован
React.memo() - Callback используется в
useMemo()
Не использовать, если callback просто передается как prop и больше нигде не применяется.
Важный нюанс с зависимостями
Часто разработчики создают циклическую зависимость:
// ПЛОХО — порочный круг:
function Counter() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(count + 1); // count в зависимостях!
}, [count]); // increment меняется при каждом count
useEffect(() => {
const timer = setInterval(increment, 1000);
return () => clearInterval(timer);
}, [increment]); // Эффект перезагружается при каждом count!
}
// ПРАВИЛЬНО — используй функциональное обновление:
function Counter() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(prev => prev + 1); // Функциональное обновление
}, []); // Зависимостей нет!
useEffect(() => {
const timer = setInterval(increment, 1000);
return () => clearInterval(timer);
}, [increment]);
}
Производительность
useCallback сам по себе имеет стоимость — он выполняет сравнение зависимостей. Если зависимостей больше, чем экономии от мемоизации, это не оправдано. Профилируй React DevTools Profiler перед оптимизацией.