Компонент рендериться повторно, коли:
- змінився його стан (
useState,useReducer); - змінився контекст, який він читає;
- перерендерився батько - за замовчуванням разом з ним рендеряться всі дочірні компоненти, незалежно від того, чи змінилися їхні props.
Третій пункт часто дивує. Але рендер - це лише виклик функції й порівняння віртуального DOM; реальний DOM змінюється тільки там, де щось справді змінилося. Зазвичай це дешево.
React.memo пропускає рендер компонента, якщо його props поверхнево не змінилися (Object.is для кожного prop):
const Chart = memo(function Chart({ data, onSelect }) {
/* важкий рендер */
});
Чому memo часто «не працює»: батько передає новий об'єкт чи функцію на кожен рендер:
<Chart data={rows.filter(isVisible)} onSelect={(id) => select(id)} />
// і data, і onSelect - нові посилання щоразу, memo марний
Тут потрібні useMemo для data і useCallback для onSelect - або передавати примітиви.
Коли memo справді доречний: компонент рендериться помітно довго і часто перерендерюється з тими самими props - великі списки, графіки, редактори. Для дрібних компонентів порівняння props може коштувати стільки ж, скільки рендер.
Альтернатива без мемоізації - правильна структура: опустити стан нижче, до компонента, якому він потрібен, або передати важку частину як children - тоді вона не перерендериться разом із батьком.
React Compiler (стабільний з 2025 року) автоматично мемоізує компоненти й значення, і там, де його ввімкнено, ручні memo/useMemo/useCallback здебільшого не потрібні.