Использование useCallback
Рассмотрим следующий пример. У нас есть родительский компонент, который передаёт функцию-действие дочернему компоненту:
import React, { useState } from 'react';
const ChildComponent = React.memo(({ onClick }: { onClick: () => void }) => {
console.log('Child re-rendered');
return <button onClick={onClick}>Click me</button>;
});
const ParentComponent = () => {
const [count, setCount] = useState(0);
const increment = () => {
console.log('Button clicked');
setCount(count + 1);
};
return (
<div>
<p>Parent count: {count}</p>
<ChildComponent onClick={increment} />
</div>
);
};
export default ParentComponent;
Кажется, всё работает, но обратите внимание: даже когда состояние count в родительском компоненте изменяется, ChildComponent перерисовывается, хотя он обёрнут в React.memo. Почему? Потому что при каждом рендере создаётся новая версия функции increment, и React считает её изменённой.
Решение с useCallback
Вот как мы можем исправить это:
const ParentComponent = () => {
const [count, setCount] = useState(0);
const increment = React.useCallback(() => {
console.log('Button clicked');
setCount((prevCount) => prevCount + 1);
}, []);
return (
<div>
<p>Parent count: {count}</p>
<ChildComponent onClick={increment} />
</div>
);
};
Теперь increment больше не создаётся заново при каждом рендере, и ChildComponent не выполняет лишние перерисовки. Это спасает производительность, особенно в больших компонентах.
Типизация с useCallback
Вот как мы можем типизировать функцию increment:
const increment: () => void = React.useCallback(() => {
console.log('Button clicked');
setCount((prevCount) => prevCount + 1);
}, []);
TypeScript автоматически выведет тип функции, но явное указание типа повышает читаемость и защищает код от ошибок.
Управление зависимостями
Не забывайте добавлять зависимости в массив второго аргумента useCallback. Если зависимость пропущена, это может вызвать неожиданные баги.
Использование useMemo
Теперь поговорим о мемоизации значений. Представьте, у нас есть компонент, который делает сложные вычисления:
const ParentComponent = () => {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(false);
// Эта функция делает тяжёлое вычисление.
const calculateValue = () => {
console.log('Calculating...');
return count * 2;
};
const result = calculateValue();
return (
<div>
<p>Result: {result}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setOtherState(!otherState)}>Toggle</button>
</div>
);
};
Каждый раз при переключении otherState вызывает повторный рендер ParentComponent, а вычисление calculateValue снова выполняется, даже если count не изменился.
Решение с useMemo
Мы можем использовать useMemo, чтобы мемоизировать значение:
const ParentComponent = () => {
const [count, setCount] = useState(0);
const [otherState, setOtherState] = useState(false);
const result = React.useMemo(() => {
console.log('Calculating...');
return count * 2;
}, [count]);
return (
<div>
<p>Result: {result}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setOtherState(!otherState)}>Toggle</button>
</div>
);
};
Теперь calculateValue вычисляется только тогда, когда значение count изменяется, а не при каждом рендере. Это существенно экономит ресурсы.
Типизация с useMemo
Чтобы типизировать значение, возвращаемое useMemo, можно сделать так:
const result: number = React.useMemo(() => count * 2, [count]);
Если вы забыли типизацию, TypeScript сам выведет тип на основе возвращаемого значения.
Когда использовать и не использовать
Используйте:
- Когда нужно избежать лишнего создания функций или значений.
- В местах с тяжёлыми вычислениями или большими группами данных.
- Для предотвращения лишних рендеров дочерних компонентов.
Не злоупотребляйте:
- Если вычисления простые или быстрые.
- Если функция не передаётся в пропсы других компонентов.
- Если профилирование показало, что нынешняя производительность достаточна.
Практическое применение
Работа с useCallback и useMemo крайне важна в реальных проектах. Например:
- В проектах с таблицами или списками, где каждый новый рендеринг влияет на производительность.
- При работе с использованием сложных формул или фильтрацией данных в реальном времени.
- В UI компонентов, которые передают функции в другие элементы (например, обработчики событий).
Эти хуки помогают сделать код чистым, производительным и лёгким в поддержке. Теперь вы на шаг ближе к тому, чтобы писать приложения, которые ускоряют работу, а не замедляют её.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ