Коротка відповідь. Майже. React Compiler 1.0 автоматично мемоізує компоненти й хуки на етапі збірки – включно з місцями, де useMemo фізично не напишеш. У новому коді ручна мемоізація здебільшого більше не потрібна. Але «пройтись по проєкту й видалити всі useMemo» – погана ідея: ручна мемоізація лишається escape hatch, насамперед для стабільних залежностей ефектів, а поведінку існуючого коду після видалення треба перевіряти, а не вгадувати. Розбираємось, де правда між «революція» і «нічого не зміниться».
Що компілятор реально робить
React Compiler 1.0 – це build-time інструмент (Babel-плагін, під капотом – власний HIR і аналіз потоків даних), який переписує твої компоненти, додаючи мемоізацію автоматично. Концептуально – ніби хтось розставив useMemo/useCallback/memo ідеально й усюди, але:
- і там, де руками не можна. Хуки не можна викликати після умовного return – а компілятор спокійно мемоізує значення й після нього:
export default function ThemeProvider(props) {
if (!props.children) {
return null;
}
// Руками useMemo тут написати неможливо – правила хуків.
// Компілятор мемоізує це без проблем.
const theme = mergeTheme(props.theme, use(ThemeContext));
return <ThemeContext value={theme}>{props.children}</ThemeContext>;
}- і з розумнішими залежностями. Optional chains (
props.user?.name) та індекси масивів компілятор трактує як повноцінні залежності – те, що в ручномуuseMemoлюди регулярно роблять неправильно.
Сумісність: React 17+ (для 17/18 потрібен пакет react-compiler-runtime і вказаний target у конфігу). Підтримка збірок: Babel, Vite, Next.js (swc-інтеграція з 15.3.1+), Rsbuild.
Що він НЕ робить
Тут більшість непорозумінь, тож прямо:
- Не скасовує useMemo/useCallback як API. Команда React явно позиціонує їх як escape hatches: для явного контролю і – найважливіше – для стабільності залежностей ефектів. Якщо об'єкт летить у
useEffectяк залежність, його ідентичність – це семантика твого коду, а не оптимізація. Детально про цю пастку – у статті про useEffect і залежності. - Не лагодить повільний код. Мемоізація прибирає зайві перерендери. Якщо один рендер твого компонента коштує 200 мс – він і буде коштувати 200 мс, просто рідше. Повільні обчислення, важкий DOM, водоспади запитів – усе це лишається твоєю роботою.
- Не гарантує «швидше» саме тобі. Meta звітує про результати на власних продуктах: до 12% швидші завантаження й навігації у Quest Store, окремі взаємодії – у 2.5 рази швидші, нейтральне споживання пам'яті. Це їхні цифри на їхньому коді – сама команда React радить експериментувати на своєму застосунку, а не переносити чужі заміри.
До/після: фільтрований список
Класика ручної мемоізації – список із фільтром і колбеками в дочірні компоненти:
// ДО: кожна залежність – ручна робота і потенційна помилка
function ProductList({ products, onBuy }: Props) {
const [query, setQuery] = useState('');
const filtered = useMemo(
() => products.filter((p) => p.name.includes(query)),
[products, query]
);
const handleBuy = useCallback((id: string) => onBuy(id), [onBuy]);
return filtered.map((p) => (
<ProductCard key={p.id} product={p} onBuy={handleBuy} />
));
}З компілятором той самий компонент пишеться так, як його написав би джуніор у перший тиждень – і це комплімент:
// ПІСЛЯ: просто логіка, без обв'язки
function ProductList({ products, onBuy }: Props) {
const [query, setQuery] = useState('');
const filtered = products.filter((p) => p.name.includes(query));
return filtered.map((p) => (
<ProductCard key={p.id} product={p} onBuy={(id) => onBuy(id)} />
));
}Компілятор сам закешує filtered по products/query і стабілізує колбек. Що перевірити після перемикання: відкрий React Profiler, набери щось в інший інпут на цій же сторінці й порівняй кількість рендерів ProductCard до і після. Вимірювання замість віри – інакше це той самий карго-культ, лише новий.
ESLint-правила: корисні навіть без компілятора
Разом із 1.0 оновився eslint-plugin-react-hooks – частина правил тепер працює на аналізі самого компілятора:
set-state-in-render– ловить setState під час рендера (нескінченні цикли);set-state-in-effect– підсвічує підозріло дорогу роботу в ефектах;refs– небезпечний доступ до ref під час рендера.
Це приємний побічний ефект: навіть якщо компілятор у build ти ще не ввімкнув, його аналізатор у лінтері вже знаходить реальні баги.
Як впроваджувати у великому проєкті
Рекомендації з офіційного гайда по incremental adoption, переказані по-людськи:
- Пін точної версії.
npm install --save-dev --save-exact babel-plugin-react-compiler@1.0.0– саме--save-exact. Компілятор переписує весь твій код; мінорне оновлення залежності з^– це не те, що хочеться отримати сюрпризом у ранковому CI. - Спочатку лінтер, потім компілятор. Онови
eslint-plugin-react-hooks, розгреби знахідки – це і підготовка коду, і безкоштовна діагностика. - Вмикай по частинах. Гейтинг по директоріях/модулях замість «увімкнули глобально в п'ятницю». Почни з нового коду і некритичних розділів.
- Старі useMemo не чіпай оптом. Офіційна позиція: або лишити як є (компілятор із ними сумісний), або видаляти точково з тестуванням. Існуючий
useMemoміг випадково бути не оптимізацією, а семантикою – стабільністю для ефекту чи контексту. - Вимірюй до і після. Profiler, свої метрики взаємодій – що саме вимірювати, детально розписано у статті про профілювання React.
Як це співіснує з ручною оптимізацією
Важливо розмежувати, бо це різні рівні роботи. У статті про оптимізацію без передчасного useMemo головна теза – «спочатку виміряй, потім оптимізуй»: структура стану, композиція через children, усвідомлені рішення на основі Profiler. Компілятор цю методологію не замінює – він прибирає з неї механічну частину.
Поділ відповідальностей тепер такий:
| Рівень | Хто відповідає |
|---|---|
| Зайві перерендери через нестабільні значення | ✅ компілятор |
| Структура стану (що де живе, що піднято) | 🧠 ти |
| Дорогі обчислення й важкий DOM | 🧠 ти |
| Стабільні залежності ефектів | 🧠 ти (useMemo/useCallback як семантика) |
| Віртуалізація, code splitting, data fetching | 🧠 ти |
Іншими словами: компілятор звільняє час від розставляння дужок – щоб ти витрачав його на рішення, які він прийняти не може. На шляху від Middle до Senior це взагалі головний патерн: цінність інженера зміщується від механіки до компромісів. І так, на код-рев'ю «а навіщо тут useMemo, якщо проєкт на компіляторі?» – уже легітимне запитання.
Типові помилки
- «Видалив усі useMemo, нічого не зламалось» – на дев-стенді. Зламається у проді на ефекті, який залежав від стабільності об'єкта. Видалення – точкове, з тестами.
- Очікування, що повільний екран стане швидким. Компілятор не прискорює рендер – він зменшує їх кількість.
^1.0.0у package.json. Див. вище: exact pin.- Увімкнули компілятор – і не перевірили лінтером. Порушення правил React, які раніше «якось працювали», з компілятором можуть поводитись інакше. Лінтер підсвітить їх до того, як це зробить продакшн.
- Перенесення чужих бенчмарків у свої обіцянки бізнесу. «Meta каже 12%» ≠ «наш застосунок стане на 12% швидшим». Виміряй – тоді обіцяй.
Практичне завдання
Візьми один свій компонент із 2–3 useMemo/useCallback. Створи поруч тестову гілку: підключи компілятор (для Next.js 15.3.1+ це конфіг-прапорець), перепиши компонент без ручної мемоізації й відкрий React Profiler. Запиши три числа: кількість рендерів дочірніх компонентів до, після, і час одного рендера. Якщо час рендера не змінився – ти щойно на власному досвіді побачив різницю між «рідше рендеримо» і «швидше рендеримо». Це розуміння цінніше за будь-який конспект.