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

Зачем использовал хук useCallBack в React?

1.7 Middle🔥 231 комментариев
#React#Оптимизация и производительность

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

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

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

Зачем использовать useCallback в React

useCallback — это хук React, который мемоизирует функцию и возвращает стабильную ссылку на неё. Это критически важно для оптимизации производительности в определённых сценариях.

Проблема: неправильные ссылки на функции

При каждом рендере функциональный компонент создаёт новые функции, даже если их код не изменился:

function ParentComponent() {
  const [count, setCount] = useState(0);

  // Эта функция создаётся заново при каждом рендере!
  const handleClick = () => {
    console.log("Нажата кнопка");
  };

  return <ChildComponent onClick={handleClick} />;
}

Проблема в том, что если ChildComponent обёрнут в React.memo (оптимизация для пропускания рендера при неизменённых props), то новая функция handleClick будет считаться "другим" объектом, и memo не сработает.

function ChildComponent({ onClick }) {
  console.log("ChildComponent перерисовался");
  return <button onClick={onClick}>Кнопка</button>;
}

// С React.memo дочерний компонент пересчитывается,
// хотя onClick логически остался тем же
export default React.memo(ChildComponent);

Решение: useCallback

useCallback создаёт стабильную ссылку на функцию и кэширует её, пока не изменятся зависимости:

function ParentComponent() {
  const [count, setCount] = useState(0);

  // handleClick останется одной и той же функцией,
  // пока count не изменится
  const handleClick = useCallback(() => {
    console.log("Счётчик:", count);
  }, [count]); // Зависимость

  return <ChildComponent onClick={handleClick} />;
}

Теперь при рендере ParentComponent, если count не изменился, handleClick будет одной и той же функцией, и React.memo не пересчитает ChildComponent.

Массив зависимостей

Это ключевой момент в useCallback. Функция будет пересоздана только если зависимости изменились:

const handleSubmit = useCallback(
  (formData) => {
    api.post("/submit", { userId, formData });
  },
  [userId] // Пересоздаём только если userId изменился
);

Если забыть указать зависимость, функция может работать с устаревшими значениями (stale closure):

// ❌ ОШИБКА: userId может быть устаревшим
const handleClick = useCallback(() => {
  console.log("userId:", userId);
}, []); // Пустые зависимости!

// ✅ ПРАВИЛЬНО
const handleClick = useCallback(() => {
  console.log("userId:", userId);
}, [userId]);

Практический пример: фильтр списка

function FilteredList() {
  const [filter, setFilter] = useState("");
  const [items, setItems] = useState([]);

  // При каждом рендере создаётся новая функция
  // Это может быть дорого, если она используется в дочерних компонентах
  const handleFilter = useCallback((searchTerm) => {
    setFilter(searchTerm);
    // Загрузка отфильтрованных данных
    fetch(`/api/items?q=${searchTerm}`)
      .then(res => res.json())
      .then(setItems);
  }, []); // Зависимостей нет, функция стабильна

  const filteredItems = items.filter(item => 
    item.name.toLowerCase().includes(filter.toLowerCase())
  );

  return (
    <div>
      <SearchInput onFilter={handleFilter} />
      <ItemList items={filteredItems} />
    </div>
  );
}

function SearchInput({ onFilter }) {
  return (
    <input
      onChange={(e) => onFilter(e.target.value)}
      placeholder="Поиск..."
    />
  );
}

const ItemList = React.memo(({ items }) => {
  // Здесь мемоизация сработает правильно,
  // т.к. onFilter (handleFilter) остаётся стабильной
  return <ul>{items.map(item => <li key={item.id}>{item.name}</li>)}</ul>;
});

Когда НЕ использовать useCallback

useCallback вводит свои затраты — функция всё равно создаётся и хранится в памяти. Используй его только когда есть явная необходимость:

  1. Передача функции в memo-компонент — это основной use case
  2. Функция в зависимостях другого эффекта — чтобы избежать бесконечных циклов
  3. Функция вызывается в списке элементов — дорого пересчитывать каждый элемент
// ❌ Ненужный useCallback
const handleChange = useCallback((e) => {
  setState(e.target.value);
}, []); // Нет memo, просто так

// ✅ Полезный useCallback
const memoChild = React.memo(({ onChange }) => {
  // Компонент не будет пересчитываться, если onChange стабильна
  return <input onChange={onChange} />;
});

function Parent() {
  const handleChange = useCallback((e) => {
    setState(e.target.value);
  }, []);
  return <memoChild onChange={handleChange} />;
}

Сравнение с useMemo

  • useCallback(fn, deps) — кэширует саму функцию
  • useMemo(fn, deps) — кэширует результат выполнения функции
// useCallback: кэшируем функцию
const memoizedCallback = useCallback(
  () => doSomething(a, b),
  [a, b]
);

// useMemo: кэшируем результат функции
const memoizedResult = useMemo(
  () => doSomething(a, b),
  [a, b]
);

Правило большого пальца

Не используй useCallback "на всякий случай" — это усложнит код без пользы. Используй профилировщик React DevTools, чтобы выявить реальные проблемы производительности, а затем применяй оптимизацию. В большинстве приложений useCallback нужен довольно редко.