TL;DR: Использование React.memo, useMemo и useCallback должно быть обоснованным. Преждевременная оптимизация может добавить накладные расходы без реальной пользы. Профилируйте перед мемоизацией и применяйте эти инструменты только там, где это действительно необходимо.
Введение: Контекст и актуальность
В мире React разработки оптимизация производительности — это важная тема. Однако часто разработчики злоупотребляют инструментами мемоизации, такими как React.memo, useMemo и useCallback, добавляя их везде, где только можно. Это может привести к обратному эффекту: вместо ускорения приложения вы получаете дополнительные накладные расходы на сравнение пропсов и использование памяти.
В этой статье мы разберем, когда использование этих инструментов действительно помогает, а когда оно только вредит производительности и читаемости кода.
Основная часть с примерами кода
React.memo: Когда это работает, а когда нет
React.memo — это Higher-Order Component (HOC), который оборачивает функциональный компонент и выполняет поверхностное сравнение пропсов перед принятием решения о повторном рендеринге. Если родительский компонент перерендеривается, но передает те же пропсы (по ссылке для объектов, по значению для примитивов), React пропускает рендеринг дочернего компонента.
const Button = React.memo(function Button({ onClick, label }) {
console.log("Button rendered");
return <button onClick={onClick}>{label}</button>;
});
function Parent() {
const [count, setCount] = useState(0);
// Это ломает мемоизацию — новый объект каждый рендер
const style = { color: "red" };
// Это нормально — примитив строка, та же ссылка
return <Button onClick={() => setCount(c => c + 1)} label="Click me" style={style} />;
}
В приведенном выше примере Button будет перерендериваться при каждом рендере Parent, несмотря на React.memo, потому что style и onClick создаются заново каждый раз.
useMemo: Для дорогих вычислений
useMemo кэширует результат вычислений между рендерами. Он пересчитывает значение только при изменении зависимостей.
const sortedItems = useMemo(() => {
return [...items].sort((a, b) => a.name.localeCompare(b.name));
}, [items]);
Важно понимать, что useMemo не делает вычисления бесплатными. Они все равно выполняются на первом рендере, и на каждом последующем рендере запускается сравнение зависимостей. Если вычисления дешевые (например, несколько арифметических операций), useMemo может стоить больше, чем он экономит.
useCallback: Стабильные ссылки на функции
useCallback по сути является useMemo для функций. Он кэширует ссылку на функцию между рендерами, чтобы идентификатор функции оставался стабильным, пока не изменятся зависимости.
const handleSelect = useCallback((id) => {
setSelectedId(id);
onSelectionChange(id);
}, [onSelectionChange]);
useCallback полезен, когда вы передаете стабильные ссылки на функции в мемоизированные дочерние компоненты. Без него даже если дочерний компонент обернут в React.memo, любая функция, переданная из родительского компонента, будет новой ссылкой на каждый рендер, что нарушит мемоизацию.
Практическое применение
Когда мемоизация помогает:
- Тяжелые компоненты: Пропуск рендеринга компонента, который занимает много времени (например, графики или таблицы данных), может быть полезным.
- Элементы списка: Если у вас есть список элементов с стабильными пропсами, мемоизация может значительно сократить количество повторных рендеров.
- Компоненты глубоко в дереве: Мемоизация помогает предотвратить каскадные рендеры в стабильных поддеревьях.
Когда мемоизация вредит:
- Компоненты с новыми пропсами на каждый рендер: Если компонент всегда получает новые пропсы (например, встроенные объекты или функции), мемоизация не поможет, но добавит накладные расходы.
- Дешевые компоненты: Если компонент рендерится быстро, сравнение пропсов может быть дороже, чем сам рендер.
- Сложные пропсы: Если пропсы сложные и их сравнение дорогое, это может быть хуже, чем просто перерендерить компонент.
Заключение
Мемоизация в React — это мощный инструмент, но его нужно использовать с умом. Прежде чем добавлять React.memo, useMemo или useCallback, всегда профилируйте ваше приложение, чтобы понять, где действительно есть проблемы с производительностью. Неоправданное использование этих инструментов может привести к увеличению накладных расходов и снижению читаемости кода. Помните: профилируйте сначала, мемоизируйте потом.
Источник: https://dev.to/helloashish99/reactmemo-usememo-usecallback-when-they-help-vs-hurt-4f6j