← Назад к вопросам

Что нужно useCallback для избежания лишних рендеров?

2.3 Middle🔥 221 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что нужно 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() — предотвращает ненужные рендеры дочерних компонентов
  • Требует правильных зависимостей — иначе функция будет устарелой
  • Не панацея — используй только когда это действительно нужно

Это мощный инструмент для оптимизации производительности, особенно в больших приложениях с множеством компонентов.