React Compiler: автоматическая мемоизация

17 июля 2026
Автор

Антон Ларичев

Что такое React Compiler

React Compiler — инструмент от команды Meta, который оптимизирует React-приложения на этапе сборки без изменения исходного кода. Он статически анализирует JSX и JavaScript, строит граф зависимостей и автоматически добавляет мемоизацию там, где она нужна.

До появления компилятора разработчики вручную оборачивали значения в useMemo, функции в useCallback, а компоненты в memo. Это требовало постоянного внимания к массивам зависимостей и легко приводило к ошибкам. React Compiler берёт эту задачу на себя.

Компилятор стал стабильным в React 19, однако экспериментальную версию можно подключить к проектам на React 17 и 18 через отдельный пакет react-compiler-runtime.

Проблема ручной мемоизации

Рассмотрим компонент без компилятора. Чтобы избежать лишних перерисовок, нужно три слоя защиты:

import { useMemo, useCallback, memo } from 'react';

interface User {
  id: number;
  name: string;
  age: number;
}

interface UserProfileProps {
  user: User;
  onUpdate: (id: number) => void;
}

const UserProfile = memo(({ user, onUpdate }: UserProfileProps) => {
  const formattedName = useMemo(() => {
    return user.name.toUpperCase();
  }, [user.name]);

  const handleClick = useCallback(() => {
    onUpdate(user.id);
  }, [onUpdate, user.id]);

  return (
    <div>
      <p>{formattedName}</p>
      <button onClick={handleClick}>Обновить</button>
    </div>
  );
});

Если забыть обновить массив зависимостей при изменении логики — появятся баги. Если добавить лишние зависимости — оптимизация перестаёт работать.

С React Compiler тот же результат достигается чистым кодом:

interface User {
  id: number;
  name: string;
  age: number;
}

interface UserProfileProps {
  user: User;
  onUpdate: (id: number) => void;
}

function UserProfile({ user, onUpdate }: UserProfileProps) {
  const formattedName = user.name.toUpperCase();

  const handleClick = () => {
    onUpdate(user.id);
  };

  return (
    <div>
      <p>{formattedName}</p>
      <button onClick={handleClick}>Обновить</button>
    </div>
  );
}

Компилятор сам определит зависимости и сгенерирует оптимизированный код.

Установка и настройка

Установка пакетов

Для React 19:

npm install -D babel-plugin-react-compiler

Для React 17/18 дополнительно нужен runtime:

npm install -D babel-plugin-react-compiler@beta
npm install react-compiler-runtime@beta

Настройка с Vite

// vite.config.js
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: ['babel-plugin-react-compiler'],
      },
    }),
  ],
});

Настройка с Next.js

В Next.js 15+ компилятор встроен:

// next.config.js
const nextConfig = {
  experimental: {
    reactCompiler: true,
  },
};

module.exports = nextConfig;

Настройка с Webpack

// babel.config.js
module.exports = {
  plugins: ['babel-plugin-react-compiler'],
};

Как работает компилятор

React Compiler анализирует код на уровне AST и для каждого выражения строит граф зависимостей. Затем генерирует оптимизированный вариант с внутренним кешированием.

Анализ зависимостей

function ProductCard({ product, discount }) {
  // finalPrice зависит от product.price и discount
  const finalPrice = product.price * (1 - discount);

  // label зависит только от product.name
  const label = `Товар: ${product.name}`;

  return (
    <div>
      <span>{label}</span>
      <span>{finalPrice}₽</span>
    </div>
  );
}

Генерация кода с кешированием

Компилятор не использует обычные useMemo и useCallback — вместо них он генерирует вызовы внутреннего хука useMemoCache, который работает эффективнее с памятью. В упрощённом виде скомпилированный код выглядит так:

// Псевдокод того, что генерирует компилятор
function ProductCard({ product, discount }) {
  const $ = useMemoCache(4);

  let finalPrice;
  if ($[0] !== product.price || $[1] !== discount) {
    finalPrice = product.price * (1 - discount);
    $[0] = product.price;
    $[1] = discount;
    $[2] = finalPrice;
  } else {
    finalPrice = $[2];
  }

  let label;
  if ($[3] !== product.name) {
    label = `Товар: ${product.name}`;
    $[3] = product.name;
  }

  return (
    <div>
      <span>{label}</span>
      <span>{finalPrice}₽</span>
    </div>
  );
}

Каждая ячейка кеша хранит предыдущее значение зависимости. При рендере компилятор сравнивает текущие значения с кешированными и пересчитывает результат только при изменении.

Правила React как основа компилятора

Компилятор работает только с кодом, который соблюдает правила React. Главное из них — чистота компонентов и хуков: никаких побочных эффектов при рендере.

Чистые компоненты

// Хорошо — компилятор оптимизирует
function Counter({ count }: { count: number }) {
  return <div>Счётчик: {count}</div>;
}

// Плохо — мутация внешней переменной при рендере
let renderCount = 0;
function BadCounter({ count }: { count: number }) {
  renderCount++; // побочный эффект, компилятор пропустит этот компонент
  return <div>Счётчик: {count}</div>;
}

Иммутабельность данных

// Хорошо — создаём новый массив
function TodoList({ todos }: { todos: string[] }) {
  const sorted = [...todos].sort();
  return <ul>{sorted.map(todo => <li key={todo}>{todo}</li>)}</ul>;
}

// Плохо — мутация входного массива
function BadTodoList({ todos }: { todos: string[] }) {
  todos.sort(); // компилятор не будет оптимизировать
  return <ul>{todos.map(todo => <li key={todo}>{todo}</li>)}</ul>;
}

Директива "use no memo"

Если нужно отключить оптимизацию для конкретного компонента, используется директива "use no memo":

function DebugComponent({ value }: { value: number }) {
  "use no memo";

  console.log('Рендер с value:', value);
  return <div>{value}</div>;
}

Это полезно при отладке, когда нужно убедиться, что компонент рендерится на каждое изменение без вмешательства компилятора.

Практические примеры

Списки с фильтрацией и сортировкой

Один из самых частых кандидатов на мемоизацию — фильтрация и сортировка массивов:

interface Product {
  id: number;
  name: string;
  price: number;
  category: string;
}

interface ProductListProps {
  products: Product[];
  category: string;
  sortBy: 'name' | 'price';
}

function ProductList({ products, category, sortBy }: ProductListProps) {
  const filtered = products
    .filter(p => p.category === category)
    .sort((a, b) => {
      if (sortBy === 'name') return a.name.localeCompare(b.name);
      return a.price - b.price;
    });

  return (
    <ul>
      {filtered.map(product => (
        <ProductItem key={product.id} product={product} />
      ))}
    </ul>
  );
}

Компилятор определит, что filtered зависит от products, category и sortBy, и будет пересчитывать его только при их изменении.

Обработчики событий в списках

interface TaskListProps {
  tasks: Array<{ id: number; title: string; done: boolean }>;
  onToggle: (id: number) => void;
  onDelete: (id: number) => void;
}

function TaskList({ tasks, onToggle, onDelete }: TaskListProps) {
  return (
    <ul>
      {tasks.map(task => (
        <li key={task.id}>
          <span style={{ textDecoration: task.done ? 'line-through' : 'none' }}>
            {task.title}
          </span>
          <button onClick={() => onToggle(task.id)}>
            {task.done ? 'Отменить' : 'Выполнить'}
          </button>
          <button onClick={() => onDelete(task.id)}>Удалить</button>
        </li>
      ))}
    </ul>
  );
}

Компилятор мемоизирует стрелочные функции внутри map с учётом task.id, onToggle и onDelete как зависимостей.

Компонент с цепочкой вычислений

interface SalesData {
  month: string;
  revenue: number;
  expenses: number;
}

interface SalesDashboardProps {
  data: SalesData[];
  taxRate: number;
}

function SalesDashboard({ data, taxRate }: SalesDashboardProps) {
  const totalRevenue = data.reduce((sum, item) => sum + item.revenue, 0);
  const totalExpenses = data.reduce((sum, item) => sum + item.expenses, 0);
  const profit = totalRevenue - totalExpenses;
  const taxAmount = profit * taxRate;
  const netProfit = profit - taxAmount;

  const bestMonth = data.reduce(
    (best, item) => (item.revenue > best.revenue ? item : best),
    data[0]
  );

  return (
    <div>
      <p>Выручка: {totalRevenue.toLocaleString('ru-RU')}₽</p>
      <p>Расходы: {totalExpenses.toLocaleString('ru-RU')}₽</p>
      <p>Прибыль до налогов: {profit.toLocaleString('ru-RU')}₽</p>
      <p>Налог ({(taxRate * 100).toFixed(0)}%): {taxAmount.toLocaleString('ru-RU')}₽</p>
      <p>Чистая прибыль: {netProfit.toLocaleString('ru-RU')}₽</p>
      <p>Лучший месяц: {bestMonth?.month}</p>
    </div>
  );
}

Вся цепочка вычислений будет пересчитана только при изменении data или taxRate.

Совместимость с существующим кодом

Что делать с имеющимися useMemo и useCallback

Компилятор не ломает существующую мемоизацию. useMemo и useCallback продолжат работать, компилятор учтёт их. Однако постепенно стоит убирать ручную мемоизацию там, где компилятор справляется сам — это упрощает код.

Плагин eslint-plugin-react-compiler подскажет, где правила React нарушены:

npm install -D eslint-plugin-react-compiler
// eslint.config.js
import reactCompiler from 'eslint-plugin-react-compiler';

export default [
  {
    plugins: {
      'react-compiler': reactCompiler,
    },
    rules: {
      'react-compiler/react-compiler': 'error',
    },
  },
];

Режим постепенного внедрения

Если не весь код проекта соответствует правилам React, можно включить компилятор только для аннотированных компонентов:

// next.config.js
const nextConfig = {
  experimental: {
    reactCompiler: {
      compilationMode: 'annotation',
    },
  },
};

В этом режиме компилятор обрабатывает только компоненты с явной директивой "use memo":

function OptimizedComponent({ value }: { value: number }) {
  "use memo";

  const computed = value * 2;
  return <div>{computed}</div>;
}

Это позволяет внедрять компилятор постепенно, не требуя одновременного исправления всего кода.

Ограничения компилятора

Существуют ситуации, когда компилятор не может оптимизировать код.

Прямые мутации состояния:

// Компилятор пропустит этот компонент
function BadComponent() {
  const [items, setItems] = useState<string[]>([]);

  const addItem = (item: string) => {
    items.push(item); // нарушение: прямая мутация state
    setItems(items);
  };

  return <div>{items.join(', ')}</div>;
}

Условные вызовы хуков:

// Нарушение правил хуков — компилятор пропустит
function ConditionalHook({ flag }: { flag: boolean }) {
  if (flag) {
    const [value] = useState(0); // хук внутри условия
    return <div>{value}</div>;
  }
  return null;
}

Сложные динамические паттерны:

// Динамические ключи объектов сложно анализировать статически
function DynamicKeys({ key, value }: { key: string; value: string }) {
  const obj = { [key]: value };
  return <div>{obj[key]}</div>;
}

Проверка работы компилятора

React DevTools поддерживает визуализацию оптимизаций компилятора. Компоненты, обработанные компилятором, отображаются со значком "Memo" в дереве компонентов.

Для предварительной оценки проекта используется инструмент react-compiler-healthcheck:

npx react-compiler-healthcheck@beta

Он выводит процент компонентов, готовых к оптимизации, и список мест, требующих исправления. Типичный вывод для здорового кода:

Found 87 components
  Successfully compiled: 79 (91%)
  Failed to compile: 8 (9%)
    - src/legacy/OldList.tsx: mutation of props
    - src/utils/BadHook.ts: conditional hook call

По мере исправления нарушений процент успешно компилируемых компонентов растёт, и приложение становится быстрее без каких-либо изменений в бизнес-логике.


Полное понимание оптимизации React — от ручной мемоизации до работы с компилятором — разбирается на курсе React на PurpleSchool.

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

Uncontrolled Components: когда DOM управляет даннымиБезопасность в React: защита от XSS, CSRF и утечек данныхRender Props: гибкое управление рендерингом в ReactРефакторинг React-кода: техники и лучшие практикиПрофилирование React: как найти и устранить узкие местаЧастичное применение: как создавать компоненты без лишнего кодаИменование компонентов в React: соглашения и лучшие практикиЛенивая загрузка: как ускорить React-приложение в разыHOC в React: мастерство композиции компонентовuseMemo: как спасти производительность от тяжелых вычисленийError Boundaries: создаем надежные React-приложенияКонтролируемые компоненты в React: полный контроль над формамиCompound Components в React: создаем гибкие компоненты с мощным APIДокументирование компонентов в React: Storybook, JSDoc и READMEКомментирование кода в React: когда и как писать комментарииКомпозиция компонентов в React: строим гибкие интерфейсыCode Splitting в React: как уменьшить бандл и ускорить загрузку приложенияАсинхронные компоненты в React: новый стандарт работы с даннымиДоступность (a11y) в React: ARIA, семантика и клавиатурная навигация
Zustand — управление состоянием в ReactZod - валидация с TypeScriptYup - валидация схемXState - конечные автоматыТемизация в ReactТестирование хуковTailwind CSS с ReactSWR - библиотека для запросовStyled Components — стилизация через JSStorybook - документация компонентовSnapshots тестированиеRTK Query - работа с APIRedux Toolkit - современный ReduxRecoil — библиотека управления состоянием от FacebookВиртуализация списков с react-window: как отображать тысячи элементов без лаговReact Toastify - уведомления в ReactReact Testing LibraryСоздание таблиц в React гайд по react-tableReact Spring - анимацииРабота с формами и селектами в ReactReact Query (TanStack Query) - работа с серверомПлагины в React что это и как их использоватьReact PDF - работа с PDF файламиОбзор популярных библиотек для ReactReact Icons - библиотека иконок для ReactReact Hook Form — валидация форм в ReactReact Dropzone — загрузка файловПодключение Bootstrap к React-приложениюReact Beautiful DnD - перетаскивание элементовАнимация при монтировании компонентов в ReactМокирование APIMobX — реактивное управление состоянием в ReactМикрофронтенды с React (micro-frontends)Загрузка и индикаторыАнимация списков в ReactJotai - атомарное состояниеБесконечная прокруткаFramer Motion - библиотека анимацийEmotion — библиотека CSS-in-JSДинамические стили в ReactE2E тестирование с CypressCSSTransition - переходыCSS-in-JS — плюсы и минусыКонтекст vs Redux — когда что использоватьИспользование Chart.js в ReactAxios с ReactТестирование асинхронных компонентовОбработка ошибок API
useState в React что это и как использоватьuseTransition - плавные переходы между состояниямиuseSyncExternalStore — работа с внешними сторамиuseRef в React — создание ссылок на DOM и значенияuseOptimistic — оптимистичные обновления UIuseLayoutEffect в React — эффект до отрисовкиuseInsertionEffect — внедрение стилей до мутаций DOMuseImperativeHandle в React — настройка ref дочернего компонентаuseId — генерация уникальных идентификаторовuseFormStatus - отслеживание статуса отправки формыuseDeferredValue — отложенное обновление состоянияuseDebugValue — отладка кастомных хуковuseCallback в React — мемоизация функцийuseReducer — альтернатива useState для сложной логикиuseMemo в React: как и когда оптимизировать тяжелые вычисленияХук use() в React 19useEffect в React что это и как использоватьuseContext — работа с контекстом в ReactuseCallback в React — мемоизация функций и оптимизация ре-рендеровuseActionState в React 19Что такое useRef и как его применять в ReactОптимизация рендеринга в React: от теории к глубокой практикеКак и зачем использовать React HooksУправление состоянием в React через ContextНовые возможности React 19Как предотвратить лишние ре-рендеры в React: полное руководствоuseMemo vs useCallback: подробное руководство по мемоизации в ReactПравила хуков — правила использованияuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в React
Transition API — плавные обновления интерфейса в ReactReact Suspense — приостановка рендераStrictMode в React — как находить ошибки на этапе разработкиСерверные компоненты React (RSC) — подробный разбор и практикаКак работает рендеринг в ReactЧто такое props в React и как их правильно использоватьКак работает JSX связка React и HTMLЧто такое React.js и как его использоватьКак использовать React DOM в проектеКак использовать элементы в ReactЧто такое компоненты в React и как их применятьРабота с children в ReactПорталы в React: рендер компонентов вне иерархии DOMFragment в React: группировка элементов без лишних узлов DOMCSS Modules в ReactConcurrent Mode — конкурентный режим в React
Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий