Разберись, когда React.memo, useMemo и useCallback действительно ускоряют приложение, а когда только зря его усложняют.
Открыть этот урок в KodokonКогда компонент перерисовывается, по умолчанию перерисовываются все его дети, независимо от того, менялись их пропсы или нет. Это не трагедия: рендер трогает DOM, только если изменился результат. Мемоизация становится уместной там, где частые рендеры встречаются с дорогими поддеревьями. Три инструмента, три цели: React.memo пропускает рендер компонента, если его пропсы поверхностно равны, useMemo кэширует вычисленное значение, useCallback кэширует функцию. Все трое опираются на равенство ссылок.
import { memo } from "react";
const Row = memo(function Row({ item, onSelect }) {
return (
<li onClick={() => onSelect(item.id)}>{item.label}</li>
);
});Цепочка прочна ровно настолько, насколько прочно её слабое звено. Если хотя бы один пропс получает новую ссылку при каждом рендере - встроенная стрелочная функция, литерал объекта или массива - сравнение в memo проваливается, и компонент всё равно перерисовывается. useCallback стабилизирует ссылку на функцию, чтобы сравнение сработало.
import { useCallback } from "react";
function List({ items }) {
const handleSelect = useCallback((id) => {
console.log("selected:", id);
}, []);
return (
<ul>
{items.map((item) => (
<Row
key={item.id}
item={item}
onSelect={handleSelect}
/>
))}
</ul>
);
}У useMemo два законных применения: избежать по-настоящему дорогого вычисления (сортировка или агрегация тысяч элементов) и стабилизировать ссылку, которую потребляет мемоизированный ребёнок или которая указана в зависимостях эффекта. Для тривиального вычисления кэш стоит дороже, чем экономит: сравнение зависимостей, удерживаемая память, более тяжёлое чтение кода.
import { useMemo } from "react";
function Stats({ orders }) {
const total = useMemo(() => {
return orders.reduce(
(sum, order) => sum + order.amount,
0
);
}, [orders]);
return <p>Total: {total} €</p>;
}