# React Compiler 1.0: чи можна нарешті перестати всюди писати useMemo і useCallback?

> React Compiler стабільний з жовтня 2025. Що він мемоізує автоматично, чого не робить, як впровадити поступово у великому проєкті – і чому «видалити всі useMemo» наступного ж дня – погана ідея.

- Автор: Юра Скиба (https://cookiesoftware.io)
- Опубліковано: 2026-10-01
- Категорія: React і Frontend
- Canonical: https://cookiesoftware.io/blog/react-compiler-usememo-usecallback

---

**Коротка відповідь.** Майже. React Compiler 1.0 автоматично мемоізує компоненти й хуки на етапі збірки – включно з місцями, де `useMemo` фізично не напишеш. У **новому** коді ручна мемоізація здебільшого більше не потрібна. Але «пройтись по проєкту й видалити всі useMemo» – погана ідея: ручна мемоізація лишається escape hatch, насамперед для стабільних залежностей ефектів, а поведінку існуючого коду після видалення треба перевіряти, а не вгадувати. Розбираємось, де правда між «революція» і «нічого не зміниться».

## Що компілятор реально робить

[React Compiler 1.0](https://react.dev/blog/2025/10/07/react-compiler-1) – це build-time інструмент (Babel-плагін, під капотом – власний HIR і аналіз потоків даних), який переписує твої компоненти, додаючи мемоізацію автоматично. Концептуально – ніби хтось розставив `useMemo`/`useCallback`/`memo` ідеально й усюди, але:

- **і там, де руками не можна.** Хуки не можна викликати після умовного return – а компілятор спокійно мемоізує значення й після нього:

```tsx
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 і залежності](/blog/useeffect-uselayouteffect).
2. **Не лагодить повільний код.** Мемоізація прибирає *зайві перерендери*. Якщо один рендер твого компонента коштує 200 мс – він і буде коштувати 200 мс, просто рідше. Повільні обчислення, важкий DOM, водоспади запитів – усе це лишається твоєю роботою.
3. **Не гарантує «швидше» саме тобі.** Meta звітує про результати на власних продуктах: до 12% швидші завантаження й навігації у Quest Store, окремі взаємодії – у 2.5 рази швидші, нейтральне споживання пам'яті. Це їхні цифри на їхньому коді – сама команда React радить експериментувати на своєму застосунку, а не переносити чужі заміри.

## До/після: фільтрований список

Класика ручної мемоізації – список із фільтром і колбеками в дочірні компоненти:

```tsx
// ДО: кожна залежність – ручна робота і потенційна помилка
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} />
  ));
}
```

З компілятором той самий компонент пишеться так, як його написав би джуніор у перший тиждень – і це комплімент:

```tsx
// ПІСЛЯ: просто логіка, без обв'язки
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](/blog/optymizatsiia-react-performance).

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

Важливо розмежувати, бо це різні рівні роботи. У [статті про оптимізацію без передчасного useMemo](/blog/optymizatsiia-react-performance) головна теза – «спочатку виміряй, потім оптимізуй»: структура стану, композиція через children, усвідомлені рішення на основі Profiler. **Компілятор цю методологію не замінює – він прибирає з неї механічну частину.**

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

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

Іншими словами: компілятор звільняє час від розставляння дужок – щоб ти витрачав його на рішення, які він прийняти не може. На [шляху від Middle до Senior](/blog/middle-to-senior-frontend) це взагалі головний патерн: цінність інженера зміщується від механіки до компромісів. І так, на [код-рев'ю](/blog/code-review-z-mentorom) «а навіщо тут 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. Запиши три числа: кількість рендерів дочірніх компонентів до, після, і час одного рендера. Якщо час рендера не змінився – ти щойно на власному досвіді побачив різницю між «рідше рендеримо» і «швидше рендеримо». Це розуміння цінніше за будь-який конспект.
