Хук useCallback предназначен для оптимизации производительности функциональных компонентов.
Он используется для мемоизирования функций, то есть для того, чтобы предотвратить их повторное создание при каждом рендере компонента.
В JavaScript функции являются объектами, и при каждом рендере компонента создаётся новая ссылка на функцию, даже если её тело не изменилось. Это может привести к ненужным повторным рендерам дочерних компонентов.
Например:
const handleClick = () => {
console.log("Кнопка нажата");
};
При каждом рендере handleClick будет новой функцией, и если её передать в мемоизированный дочерний компонент, он перерендерится, даже если логика не изменилась.
Хук useCallback решает эту проблему, возвращая ту же ссылку на функцию, пока не изменятся зависимости.
Это позволяет изолировать ресурсоемкие функции, чтобы они не исполнялись автоматически при каждом рендеринге.
Хуки useCallback и useMemo похожи:
useMemoвозвращает мемоизированное значение.useCallbackвозвращает мемоизированную функцию.
Подробнее о useMemo читайте в разделе про хук useMemo.
Синтаксис
Хук useCallback принимает два аргумента:
useCallback(callback, dependencies)
callback: функция, которую вы хотите мемоизировать.dependencies: массив зависимостей для функции. Мемоизированная функция будет изменяться только в том случае, если изменилось хотя бы одно из этих зависимостей.
Пример
Без использования useCallback
import React, { useState } from 'react';
import { createRoot } from 'react-dom/client';
// Дочерний компонент, принимающий пропсы функции
const Button = React.memo(({ onClick, text }) => {
alert(`Дочерняя кнопка ${text} перерисована`);
return <button onClick={onClick}>{text}</button>;
});
// Родительский компонент без useCallback
function WithoutCallbackExample() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
// Эта функция воссоздается при каждом рендеринге
const handleClick1 = () => {
setCount1(count1 + 1);
};
const handleClick2 = () => {
setCount2(count2 + 1);
};
alert("Родитель перерисован");
return (
<div>
<h2>Без useCallback:</h2>
<p>Счётчик 1: {count1}</p>
<p>Счётчик 2: {count2}</p>
<Button onClick={handleClick1} text="Кнопка 1" />
<Button onClick={handleClick2} text="Кнопка 2" />
</div>
);
}
createRoot(document.getElementById('root')).render(
<WithoutCallbackExample />
);
Попробуйте запустить пример выше и нажмите на кнопки.
Вы заметите, что все три компонента (родитель и обе кнопки) перерисовываются каждый раз, когда вы нажимаете на кнопки.
Этого можно избежать, используя хук useCallback.
Используя useCallback, мы можем мемоизировать функции и восстанавливать их только при изменении зависимостей.
При нажатии на первую кнопку должны перерисоваться только родитель и первая кнопка, а при нажатии на вторую кнопку — только родитель и вторая кнопка.
Пример
C использованием useCallback
import React, { useState, useCallback } from 'react';
import { createRoot } from 'react-dom/client';
// Дочерний компонент, принимающий пропсы функции
const Button = React.memo(({ onClick, text }) => {
console.log(`Кнопка ${text} перерисована`);
return <button onClick={onClick}>{text}</button>;
});
// Родительский компонент с useCallback
function WithCallbackExample() {
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
// Эти функции мемоизированы и воссоздаются только при изменении зависимостей
const handleClick1 = useCallback(() => {
setCount1(count1 + 1);
}, [count1]);
const handleClick2 = useCallback(() => {
setCount2(count2 + 1);
}, [count2]);
console.log("Родитель перерисован");
return (
<div>
<h2>С useCallback:</h2>
<p>Счётчик 1: {count1}</p>
<p>Счётчик 2: {count2}</p>
<Button onClick={handleClick1} text="Кнопка 1" />
<Button onClick={handleClick2} text="Кнопка 2" />
</div>
);
}
createRoot(document.getElementById('root')).render(
<WithCallbackExample />
);
Когда использовать useCallback
Используйте useCallback в следующих случаях:
- Передача функции в
React.memo-компонент
Чтобы избежать ненужных ререндеров дочерних компонентов. - Функция используется как зависимость в
useEffect
Например:useEffect(() => { someAsyncOperation(); }, [someCallback]); // someCallback должна быть мемоизирована - Работа с пользовательским хуками, возвращающими функции, которые могут использоваться в других хуках.
- Оптимизация списков, где каждому элементу передаётся функция (например, кнопка "удалить").
Когда НЕ нужно использовать useCallback
- Если функция передаётся в обычный (немемоизированный) компонент.
- Если компонент рендерится редко или функция простая.
- "На всякий случай" — это не оправдывает затраты на чтение и поддержку кода.
useCallback — это инструмент оптимизации, а не обязательная практика. Чрезмерное использование может усложнить код без реальной пользы.
Отличие от useMemo
| Хук | Возвращает | Назначение |
|---|---|---|
useCallback | Мемоизированную функцию | Оптимизация передачи функций |
useMemo | Вычисленное значение | Оптимизация тяжёлых вычислений (например, сортировка массива) |
Пример useMemo:
const expensiveValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Лучшие практики
- Всегда указывайте все зависимости, от которых зависит функция.
- Не мемоизируйте каждую функцию — только те, где это критично для производительности.
- Используйте
useCallbackв связке сReact.memo()для максимального эффекта.
Хук useCallback помогает оптимизировать React-приложения, предотвращая ненужные ререндеры за счёт сохранения ссылки на функцию между рендерами. Его стоит применять осознанно, в тех случаях, где это действительно необходимо для производительности.