← Назад к вопросам
Как useCallback помогает оптимизировать дочерние компоненты?
2.0 Middle🔥 221 комментариев
#React
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как useCallback помогает оптимизировать дочерние компоненты?
useCallback — это хук React, который мемоизирует функции, предотвращая их пересоздание на каждом рендере. Это критически важно для оптимизации производительности при передаче функций в дочерние компоненты, обёрнутые в React.memo.
Проблема без useCallback
Когда вы передаёте функцию в дочерний компонент, она пересоздаётся на каждом рендере родителя:
const Parent = () => {
const [count, setCount] = useState(0);
// Эта функция пересоздаётся на КАЖДОМ рендере
const handleClick = () => {
console.log('Clicked');
};
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child onClick={handleClick} /> {/* Каждый раз передаём ДРУ ГУЮ функцию */}
</div>
);
};
const Child = React.memo(({ onClick }) => {
console.log('Child rendering');
return <button onClick={onClick}>Click me</button>;
});
// Результат:
// При нажатии на кнопку count меняется
// Parent перерендерится
// handleClick пересоздаётся (новая функция с новой ссылкой)
// React.memo видит: prevProps.onClick !== nextProps.onClick
// Child перерендерится БЕЗ необходимости!
Решение: useCallback
useCallback мемоизирует функцию, пока её зависимости не изменятся:
const Parent = () => {
const [count, setCount] = useState(0);
// Функция пересоздаётся ТОЛЬКО если 'count' изменится
const handleClick = useCallback(() => {
console.log('Clicked with count:', count);
}, [count]); // Зависимости
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<Child onClick={handleClick} /> {/* Передаём стабильную функцию */}
</div>
);
};
const Child = React.memo(({ onClick }) => {
console.log('Child rendering');
return <button onClick={onClick}>Click me</button>;
});
// Результат:
// При нажатии на кнопку count меняется
// Parent перерендерится
// useCallback видит: count изменился
// Функция пересоздаётся
// React.memo видит: prevProps.onClick !== nextProps.onClick
// Child перерендерится (есть причина)
Практический пример: Список с добавлением элементов
const TodoApp = () => {
const [todos, setTodos] = useState([]);
const [inputValue, setInputValue] = useState('');
// НЕПРАВИЛЬНО - без useCallback
const addTodoWrong = () => {
setTodos([...todos, { id: Date.now(), title: inputValue }]);
setInputValue('');
};
// ПРАВИЛЬНО - с useCallback
const addTodo = useCallback(() => {
setTodos(prev => [...prev, { id: Date.now(), title: inputValue }]);
setInputValue('');
}, [inputValue]); // Зависит от inputValue
return (
<div>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder="Add todo"
/>
<AddButton onClick={addTodo} /> {/* Дочерний компонент */}
<TodoList todos={todos} />
</div>
);
};
// React.memo защищает от ненужных рендеров
const AddButton = React.memo(({ onClick }) => {
console.log('AddButton rendering');
return <button onClick={onClick}>Add</button>;
});
const TodoList = React.memo(({ todos }) => {
console.log('TodoList rendering');
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
});
Сравнение: с useCallback vs без
const Parent = () => {
const [parentCount, setParentCount] = useState(0);
const [childState, setChildState] = useState(0);
// ВАРИАНТ 1: БЕЗ useCallback
const handleChildClick = () => {
setChildState(childState + 1);
};
// ВАРИАНТ 2: С useCallback
const handleChildClickOptimized = useCallback(() => {
setChildState(prev => prev + 1);
}, []); // Зависимостей нет!
return (
<div>
<p>Parent count: {parentCount}</p>
<button onClick={() => setParentCount(parentCount + 1)}>
Increment parent (triggers parent rerender)
</button>
{/*
БЕЗ useCallback:
Каждый раз, когда parentCount меняется,
Parent перерендерится и пересоздаст handleChildClick
Child перерендерится даже если изменилась только родительское состояние
*/}
<Child onClick={handleChildClick} />
{/*
С useCallback и пустыми зависимостями:
handleChildClickOptimized НИКОГДА не пересоздаётся
Child рендерится ТОЛЬКО если childState меняется
*/}
<Child onClick={handleChildClickOptimized} />
</div>
);
};
const Child = React.memo(({ onClick }) => {
console.log('Child rendering');
return <button onClick={onClick}>Click child</button>;
});
useCallback с правильными зависимостями
const UserManager = () => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
// Функция зависит от userId
const fetchUser = useCallback(
async (userId) => {
setLoading(true);
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
setLoading(false);
},
[] // Нет зависимостей (параметр передаётся явно)
);
// Функция зависит от user объекта
const deleteUser = useCallback(() => {
if (user) {
fetch(`/api/users/${user.id}`, { method: 'DELETE' });
setUser(null);
}
}, [user]); // Зависит от user
// Функция с несколькими зависимостями
const updateUser = useCallback(
(updates) => {
setUser(prev => ({ ...prev, ...updates }));
},
[] // Обновление состояния, зависимостей нет
);
return (
<div>
<UserProfile user={user} onFetch={fetchUser} onUpdate={updateUser} />
<button onClick={() => deleteUser()}>Delete</button>
{loading && <p>Loading...</p>}
</div>
);
};
const UserProfile = React.memo(({ user, onFetch, onUpdate }) => {
useEffect(() => {
if (user) {
onFetch(user.id);
}
}, [user.id, onFetch]); // onFetch стабильна благодаря useCallback
return (
<div>
{user && <h2>{user.name}</h2>}
<button onClick={() => onUpdate({ name: 'Updated' })}>Update</button>
</div>
);
});
Когда НЕ нужен useCallback
const Component = () => {
// НЕ нужен useCallback для простых случаев
const handleClick = () => console.log('clicked');
return <button onClick={handleClick}>Click</button>;
};
// useCallback имеет смысл ТОЛЬКО если:
// 1. Функция передаётся дочернему компоненту
// 2. Дочерний компонент обёрнут в React.memo
// 3. Функция используется в зависимостях других хуков (useEffect, useMemo)
Проблемы неправильного использования useCallback
// ПРОБЛЕМА 1: Лишние зависимости
const wrong = useCallback(() => {
console.log(data1, data2, data3); // 3 объекта
}, [data1, data2, data3]); // Функция пересоздаётся часто
// ЛУЧШЕ: Использовать функциональное обновление
const better = useCallback(() => {
setData(prev => ({ ...prev, field: newValue }));
}, []); // Нет зависимостей
// ПРОБЛЕМА 2: Неправильные зависимости
const wrongDeps = useCallback(
(userId) => fetch(`/api/users/${userId}`),
[] // Забыли apiUrl если он переменный
);
// ПРОБЛЕМА 3: useCallback на всё подряд
const Component = () => {
// Не нужен useCallback для простых функций,
// которые не передаются в дочерние компоненты
const simple = useCallback(() => {}, []); // Пустая трата памяти
};
Итог
useCallback помогает оптимизировать дочерние компоненты:
- Предотвращает пересоздание функций — одна и та же ссылка на функцию
- Работает с React.memo — дочерний компонент видит, что props не изменился
- Стабильные зависимости для useEffect — избегаем бесконечных циклов
- Значит только с дочерними компонентами — не используй везде
- Требует правильных зависимостей — иначе код будет неправильным
Используй useCallback стратегически: только для функций, передаваемых в React.memo компоненты или используемых в зависимостях других хуков.