useCallback — это хук в React, который позволяет мемоизировать (кэшировать) функции между рендерами. Он предотвращает создание новых экземпляров функций при каждом рендере, что помогает оптимизировать производительность и избежать лишних ререндеров дочерних компонентов.
Синтаксис:
const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);Ключевые особенности:
Пример использования:
function ParentComponent({ data }) {
const handleClick = useCallback(() => {
console.log('Кнопка нажата');
}, []);
return <ChildComponent onClick={handleClick} />;
}Хук useCallback — один из встроенных хуков React, предназначенный для оптимизации производительности путем мемоизации экземпляров функций. Он помогает избежать создания новых экземпляров функций при каждом рендере компонента, что особенно важно при передаче колбэков в оптимизированные дочерние компоненты. 🚀
useCallback принимает две функции:
import React, { useCallback, useState } from 'react';
function Component() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []); // Функция создается только один раз
return (
<div>
<p>Счетчик: {count}</p>
<button onClick={handleClick}>Увеличить</button>
</div>
);
}Когда компонент рендерится:
function Parent() {
const [count, setCount] = useState(0);
// Без useCallback - новая функция при каждом рендере
// const increment = () => setCount(prev => prev + 1);
// С useCallback - стабильная ссылка на функцию
const increment = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return (
<div>
<p>Счетчик: {count}</p>
<ChildComponent onIncrement={increment} />
</div>
);
}
// Дочерний компонент оптимизирован с React.memo
const ChildComponent = React.memo(function ChildComponent({ onIncrement }) {
console.log('ChildComponent отрендерен');
return <button onClick={onIncrement}>Увеличить</button>;
});function DataFetcher({ userId }) {
const [data, setData] = useState(null);
const fetchUserData = useCallback(async () => {
const response = await fetch(`/api/users/${userId}`);
const userData = await response.json();
setData(userData);
}, [userId]);
// useEffect не будет пересоздавать подписку при изменении fetchUserData
useEffect(() => {
fetchUserData();
}, [fetchUserData]);
return <div>{data ? data.name : 'Загрузка...'}</div>;
}function TodoList({ todos, onToggle }) {
return (
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={useCallback(() => onToggle(todo.id), [onToggle, todo.id])}
/>
))}
</ul>
);
}
const TodoItem = React.memo(function TodoItem({ todo, onToggle }) {
return (
<li>
<label>
<input
type="checkbox"
checked={todo.completed}
onChange={onToggle}
/>
{todo.text}
</label>
</li>
);
});✅ Используйте useCallback когда:
function ExpensiveComponent({ items, onUpdate }) {
// Стабильный колбэк для оптимизированных дочерних компонентов
const handleUpdate = useCallback((id, value) => {
onUpdate(id, value);
}, [onUpdate]);
return (
<div>
{items.map(item => (
<MemoizedItem
key={item.id}
item={item}
onUpdate={handleUpdate}
/>
))}
</div>
);
}
const MemoizedItem = React.memo(function MemoizedItem({ item, onUpdate }) {
return (
<div>
<span>{item.name}</span>
<button onClick={() => onUpdate(item.id, 'новое значение')}>
Обновить
</button>
</div>
);
});❌ Избегайте useCallback когда:
// ❌ Не стоит использовать useCallback без необходимости
function SimpleComponent() {
const handleClick = useCallback(() => {
console.log('Нажато');
}, []); // Избыточно для простого компонента
return <button onClick={handleClick}>Нажми меня</button>;
}
// ✅ Просто используйте обычную функцию
function SimpleComponent() {
const handleClick = () => {
console.log('Нажато');
};
return <button onClick={handleClick}>Нажми меня</button>;
}// ❌ Избыточное использование
function Component() {
const handleSubmit = useCallback(() => {
console.log('Форма отправлена');
}, []); // Не передается в оптимизированные компоненты
return (
<form onSubmit={handleSubmit}>
<input type="text" />
<button type="submit">Отправить</button>
</form>
);
}
// ✅ Просто используйте обычную функцию
function Component() {
const handleSubmit = () => {
console.log('Форма отправлена');
};
return (
<form onSubmit={handleSubmit}>
<input type="text" />
<button type="submit">Отправить</button>
</form>
);
}// ❌ Ошибка: отсутствует зависимость
function Component({ userId }) {
const fetchUser = useCallback(async () => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}, []); // userId не в списке зависимостей!
return <UserDisplay fetchUser={fetchUser} />;
}
// ✅ Правильно: все зависимости указаны
function Component({ userId }) {
const fetchUser = useCallback(async () => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
}, [userId]); // userId в зависимостях
return <UserDisplay fetchUser={fetchUser} />;
}// ❌ Ошибка: устаревшее замыкание
function Component({ userId }) {
const fetchUser = useCallback(async () => {
// userId всегда будет начальным значением
const response = await fetch(`/api/users/${userId}`);
return response.json();
}, []); // Пустые зависимости
return <UserDisplay fetchUser={fetchUser} />;
}
// ✅ Правильно: включите изменяющиеся зависимости
function Component({ userId }) {
const fetchUser = useCallback(async () => {
// userId будет текущим значением
const response = await fetch(`/api/users/${userId}`);
return response.json();
}, [userId]); // Включите userId
return <UserDisplay fetchUser={fetchUser} />;
}✅ useCallback — это хук React для:
✅ Когда использовать:
❌ Когда избегать:
✅ Лучшие практики:
useCallback — мощный инструмент оптимизации, но его не следует использовать бездумно. Сначала определите реальные проблемы с производительностью, а затем применяйте useCallback для их решения. 🚀
Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice, добавляйте сайт в закладки и совершенствуйтесь каждый день 💪