Коротка відповідь. Майже. 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.

Що він НЕ робить

Тут більшість непорозумінь, тож прямо:

  1. Не скасовує useMemo/useCallback як API. Команда React явно позиціонує їх як escape hatches: для явного контролю і – найважливіше – для стабільності залежностей ефектів. Якщо об'єкт летить у useEffect як залежність, його ідентичність – це семантика твого коду, а не оптимізація. Детально про цю пастку – у статті про useEffect і залежності.
  2. Не лагодить повільний код. Мемоізація прибирає зайві перерендери. Якщо один рендер твого компонента коштує 200 мс – він і буде коштувати 200 мс, просто рідше. Повільні обчислення, важкий DOM, водоспади запитів – усе це лишається твоєю роботою.
  3. Не гарантує «швидше» саме тобі. 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, переказані по-людськи:

  1. Пін точної версії. npm install --save-dev --save-exact babel-plugin-react-compiler@1.0.0 – саме --save-exact. Компілятор переписує весь твій код; мінорне оновлення залежності з ^ – це не те, що хочеться отримати сюрпризом у ранковому CI.
  2. Спочатку лінтер, потім компілятор. Онови eslint-plugin-react-hooks, розгреби знахідки – це і підготовка коду, і безкоштовна діагностика.
  3. Вмикай по частинах. Гейтинг по директоріях/модулях замість «увімкнули глобально в п'ятницю». Почни з нового коду і некритичних розділів.
  4. Старі useMemo не чіпай оптом. Офіційна позиція: або лишити як є (компілятор із ними сумісний), або видаляти точково з тестуванням. Існуючий useMemo міг випадково бути не оптимізацією, а семантикою – стабільністю для ефекту чи контексту.
  5. Вимірюй до і після. Profiler, свої метрики взаємодій – що саме вимірювати, детально розписано у статті про профілювання React.

Як це співіснує з ручною оптимізацією

Важливо розмежувати, бо це різні рівні роботи. У статті про оптимізацію без передчасного useMemo головна теза – «спочатку виміряй, потім оптимізуй»: структура стану, композиція через children, усвідомлені рішення на основі Profiler. Компілятор цю методологію не замінює – він прибирає з неї механічну частину.

Поділ відповідальностей тепер такий:

Рівень Хто відповідає
Зайві перерендери через нестабільні значення ✅ компілятор
Структура стану (що де живе, що піднято) 🧠 ти
Дорогі обчислення й важкий DOM 🧠 ти
Стабільні залежності ефектів 🧠 ти (useMemo/useCallback як семантика)
Віртуалізація, code splitting, data fetching 🧠 ти

Іншими словами: компілятор звільняє час від розставляння дужок – щоб ти витрачав його на рішення, які він прийняти не може. На шляху від Middle до Senior це взагалі головний патерн: цінність інженера зміщується від механіки до компромісів. І так, на код-рев'ю «а навіщо тут useMemo, якщо проєкт на компіляторі?» – уже легітимне запитання.

Типові помилки

  1. «Видалив усі useMemo, нічого не зламалось» – на дев-стенді. Зламається у проді на ефекті, який залежав від стабільності об'єкта. Видалення – точкове, з тестами.
  2. Очікування, що повільний екран стане швидким. Компілятор не прискорює рендер – він зменшує їх кількість.
  3. ^1.0.0 у package.json. Див. вище: exact pin.
  4. Увімкнули компілятор – і не перевірили лінтером. Порушення правил React, які раніше «якось працювали», з компілятором можуть поводитись інакше. Лінтер підсвітить їх до того, як це зробить продакшн.
  5. Перенесення чужих бенчмарків у свої обіцянки бізнесу. «Meta каже 12%» ≠ «наш застосунок стане на 12% швидшим». Виміряй – тоді обіцяй.

Практичне завдання

Візьми один свій компонент із 2–3 useMemo/useCallback. Створи поруч тестову гілку: підключи компілятор (для Next.js 15.3.1+ це конфіг-прапорець), перепиши компонент без ручної мемоізації й відкрий React Profiler. Запиши три числа: кількість рендерів дочірніх компонентів до, після, і час одного рендера. Якщо час рендера не змінився – ти щойно на власному досвіді побачив різницю між «рідше рендеримо» і «швидше рендеримо». Це розуміння цінніше за будь-який конспект.