React Compiler против ручной мемоизации
Долгие годы оптимизация производительности React сводилась к одному: расставлять useMemo, useCallback и React.memo по всей кодовой базе и надеяться, что массивы зависимостей указаны правильно. React Compiler меняет это уравнение. Но насколько? И есть ли ещё место ручной мемоизации в современных React-приложениях?
Вот что вам действительно нужно знать.
Ключевые выводы
- React Compiler — это инструмент времени сборки, который автоматически применяет эквивалент
React.memo,useMemoиuseCallbackна основе статического анализа. - Он отлично справляется с распространёнными паттернами: callback-пропсами, children как пропсами и возвращаемыми значениями кастомных хуков.
- Ручная мемоизация по-прежнему важна для сторонних хуков, возвращающих нестабильные объекты, для зависимостей эффектов и для узких мест, выявленных профилированием.
- Новый подход: пишите чистые компоненты по умолчанию, а мемоизируйте осознанно — когда есть измеримая причина.
Что делает React Compiler
React Compiler — это инструмент времени сборки, который автоматически мемоизирует ваши компоненты, их пропсы и значения, возвращаемые хуками. Он анализирует ваш код на этапе компиляции и применяет оптимизации, эквивалентные оборачиванию в React.memo, useMemo и useCallback, при этом вам не нужно писать ни строчки такого кода.
Сейчас он стабилен и готов к продакшену: используется в проде в Meta и поддерживается в Babel, Vite, Metro и Rsbuild. Next.js 15.3.1+ поддерживает путь вызова React Compiler через SWC для улучшения производительности сборки.
Ключевое слово здесь — время сборки. React Compiler — это не рантайм-кэш для произвольных значений. Он сосредоточен именно на оптимизации повторных рендеров компонентов на основе статического анализа структуры вашего кода.
Где React Compiler автоматически справляется с мемоизацией
Компилятор аккуратно обрабатывает типовые случаи:
- Простые изменения состояния — соседний компонент, который не зависит от изменившегося состояния, не будет перерендериваться.
- Пропсы с колбэками — встраиваемые стрелочные функции, передаваемые как пропсы, корректно мемоизируются, даже во вложенных случаях.
- Children как пропсы — печально известный сложный паттерн
useMemo(() => <Child />, [])становится ненужным. - Кастомные хуки — возвращаемые значения мемоизируются на основе их реальных зависимостей.
На третьем случае стоит остановиться. Большинство разработчиков делают это вручную неправильно. Компилятор делает это правильно автоматически.
Где ручная мемоизация в React по-прежнему важна
Реальное тестирование на нескольких кодовых базах рисует более приземлённую картину. Компилятор хорошо справляется с изолированными, самодостаточными компонентами. Он испытывает трудности, когда сторонние библиотеки возвращают немемоизированные объекты.
Самый показательный пример: useMutation из React Query возвращает новый объект при каждом рендере. Если ваш колбэк onDelete зависит от deleteCountryMutation, а не от деструктурированной функции mutate напрямую, компилятор не сможет его стабилизировать. Исправление простое, как только вы его знаете: деструктурируйте mutate напрямую. Но компилятор не может принять это решение за вас.
// Compiler-friendly: stable reference
const { mutate: deleteCountry } = useMutation(...)
// Compiler-unfriendly: new object every render
const deleteCountryMutation = useMutation(...)
const onDelete = () => deleteCountryMutation.mutate(name)
Другие случаи, где ручной контроль по-прежнему выигрывает:
- Зависимости эффектов — когда вам нужно мемоизированное значение именно для того, чтобы
useEffectне срабатывал многократно. - Динамические списки — строки, отрисованные внутри
.map(), выигрывают от выделения в именованные компоненты со стабильными пропсамиkey. Компилятор оптимизирует внутри компонентов лучше, чем между инлайн-выводом рендера. - Тонкая настройка производительности после профилирования — если вы измерили конкретное узкое место, явный
useMemoдаёт точный контроль, недостижимый для эвристик компилятора.
Discover how at OpenReplay.com.
Как должны измениться ваши привычки кодирования на React
Сдвиг ментальной модели прост: перестаньте мемоизировать защитно, начните мемоизировать осознанно.
До появления компилятора по умолчанию было принято оборачивать всё на всякий случай. Это добавляло шум, нагрузку на сопровождение и иногда приводило к тонким багам (например, ловушка с useCallback и встраиваемой стрелочной функцией, на которую теперь обращает внимание официальная документация).
С включённым React Compiler новый дефолт — писать чистые, простые компоненты и позволять компилятору заниматься оптимизацией. Прибегайте к useMemo или useCallback, когда у вас есть конкретная, измеримая причина, а не рефлекторно.
Две практические привычки, которые стоит выработать уже сейчас:
- Выделяйте элементы списков в именованные компоненты —
<CountryRow />вместо инлайн-JSX внутри.map(). - Деструктурируйте стабильные значения из сторонних хуков — используйте
mutateнапрямую, а не весь объект мутации.
Практический вывод
React Compiler — это реальное улучшение оптимизации производительности React. Он устраняет большую часть защитной мемоизации, которая загромождала кодовые базы, и обрабатывает сложный паттерн children-as-props лучше, чем большинство разработчиков делают это вручную.
Но он не заменяет понимание того, как работают повторные рендеры в React. Больше всего от компилятора получат те разработчики, которые по-прежнему понимают компромиссы между useMemo и React Compiler и знают, к чему обращаться в каждом случае.
Заключение
React Compiler знаменует настоящий поворотный момент в том, как мы думаем о производительности в React. Рефлекторная привычка оборачивать каждое значение и функцию в хелпер мемоизации больше не является правильным дефолтом. Пишите более простой код, дайте компилятору делать своё дело и профилируйте перед ручной оптимизацией. Держите ручную мемоизацию в своём инструментарии для случаев, которые компилятор не видит, особенно вокруг сторонних хуков и измеренных узких мест. Это подход, который остаётся актуальным в 2026 году и далее.
FAQ
Да. Компилятор обрабатывает большую часть защитной мемоизации, но вам всё равно необходимо понимать эти хуки для случаев, которые он не может оптимизировать: нестабильные объекты, возвращаемые сторонними библиотеками, зависимости эффектов и измеренные узкие места производительности. Понимание того, как работают повторные рендеры, также помогает писать код, который компилятор сможет оптимизировать эффективнее.
Нет. React Compiler спроектирован так, чтобы сосуществовать с существующими вызовами useMemo, useCallback и React.memo. Вы можете внедрять его постепенно, не удаляя текущую мемоизацию. Со временем вы сможете убрать избыточную ручную мемоизацию, но нет необходимости срочно вычищать её перед включением компилятора в проекте.
Объект мутации, возвращаемый useMutation, имеет новую ссылку при каждом рендере. Компилятор не может определить, что его внутренние методы стабильны, поэтому любой колбэк, зависящий от всего объекта, пересоздаётся. Деструктуризация стабильной функции mutate напрямую из useMutation решает эту проблему и позволяет компилятору корректно мемоизировать зависимые колбэки.
Он поддерживает Babel, Vite, Metro и Rsbuild, а Next.js 15.3.1 и более поздние версии включают его через SWC. Большинство современных React-конфигураций могут внедрить его с минимальной настройкой. Перед интеграцией в продакшен-код сверьтесь с официальной документацией React Compiler, чтобы узнать актуальный список поддерживаемых тулчейнов и специфичные для фреймворка шаги настройки.
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.