Будущее React
Fluent React: Build Fast, Performant, and Intuitive Web Applications
Глубокое погружение во внутреннее устройство React: JSX и продвинутые паттерны, виртуальный DOM и реконциляция, серверный рендеринг, конкурентный режим и серверные компоненты.
Примет ли React сигналы?
Реактивные примитивы вроде сигналов разошлись по всей интерфейсной экосистеме — логично предположить, что React в конечном итоге примет аналогичный «сигнальный» подход. Однако команда React заявила, что «не в восторге» от сигналов, и выбирает альтернативную технологию, чтобы добиться тех же преимуществ в производительности, которые обеспечивают сигналы.
- Сначала подытожим на примере, как React обновляет интерфейс — и где он теряет производительность.
- Посмотрим на ручное решение через memo и на его цену.
- Разберём философию команды React: почему сигналы для них — деталь реализации.
- Познакомимся с React Forget — компилятором, который должен решить задачу за нас.
- И честно взвесим Forget против сигналов — вместе со скепсисом автора книги.
Грубая реактивность на очень замысловатом примере
import React, { useState } from "react"; import { ComponentWithExpensiveChildren } from "./ExpensiveComponent"; function Counter() { const [count, setCount] = useState(0); function increment() { setCount(count + 1); } return ( <div> <p>{count}</p> <button onClick={increment}>Increment</button> <ComponentWithExpensiveChildren /> </div> ); }
- В компоненте три дочерних элемента: <p> со значением счётчика, <button>, который его увеличивает, и <ComponentWithExpensiveChildren> — он отображает дорогостоящие дочерние элементы с большим количеством вычислений.
- Нажимаем кнопку. Функция Counter вызывается заново — вместе со всеми её дочерними элементами. Это поведение React по умолчанию.
- Значит, ComponentWithExpensiveChildren отрисовывается повторно, хотя в этом нет необходимости: его пропсы и состояние не изменились.
memo работает — пока мы про него помним
Исправить это довольно легко: включаем memo в нужное время и в нужном месте.
import React, { useState, memo } from "react"; function Counter() { const [count, setCount] = useState(0); return ( <div> <p>{count}</p> <button onClick={() => setCount(count + 1)}>+1</button> <MemoizedComponentWithExpensiveChildren /> </div> ); } const MemoizedComponentWithExpensiveChildren = memo( ComponentWithExpensiveChildren );
Что получилось
В этом случае обеспечивается такая же детализированная реактивность, как при использовании сигналов: дорогим поддеревом React больше не занимается, пока его пропсы не изменились.
Чего это стоит
Это работает до тех пор, пока мы не забываем использовать memo везде, где нам нужно. И потому это не так удобно, как сигналы: мемоизация — обязанность разработчика, а не свойство модели.
Философия команды React
Многим кажется, что сигналы легко решили бы эту проблему. Но команда React в Meta считает, что сигналы, как и memo, могут быть деталями реализации, о которых обычным разработчикам задумываться не следует. Они прислушиваются к первоначальному ценностному предложению React.
Лучший способ — когда думать не надо
Команда React считает, что лучший способ — это когда разработчики не заботятся о сигналах, memo или каких-либо деталях, а фреймворк должен быть в состоянии сам найти оптимальный способ визуализации интерфейса.
Значит, задачу решает инструмент
С этой целью команда работает над новым программным обеспечением, предназначенным именно для этого. Речь идёт о React Forget.
Линтер с включённым флагом --fix
Forget — это набор инструментов для React, похожий на линтер с включённым --fix. Он применяет правила React, а затем автоматически преобразует код React в оптимальный, интеллектуально запоминая значения, которые не будут меняться на протяжении жизненного цикла приложения — такие как ComponentWithExpensiveChildren.
Именно благодаря правилам компилятор может предсказать неизменные значения и сохранить их в памяти за нас. Каковы эти правила?
- Компоненты React предполагаются чистыми функциями; некоторые хуки и пользовательские обработчики событий чистыми быть не обязаны.
- Запрещено: изменять переменные и объекты, которые не были созданы в функции заново; читать свойства, которые могут измениться.
- Разрешено: читать свойства или состояние; генерировать ошибки; мутировать вновь созданные объекты и привязки.
- Исключение: отложенная (ленивая) инициализация — здесь мутация с целью инициализации допускается.
- Объекты и замыкания, созданные во время рендеринга, не должны изменяться после его завершения — кроме изменяемых объектов, сохранённых в состоянии.
Forget против сигналов
На момент написания книги Forget был закрытым: он проходил оценку в Meta и превосходил ожидания по использованию в Instagram и WhatsApp, а команда рассматривала возможность выпустить его как open source. Судить о компромиссах авторитетно было сложно — но предположить можно.
Почему сигналы всё равно могут выигрывать
Даже если Forget запомнит всё, что не меняется, сигналы живут в параллельной вселенной за пределами иерархии компонентов. Поэтому при обновлении React всё равно придётся пройти по всему дереву компонентов и сравнить старые и новые пропсы каждого, чтобы определить, кто нуждается в повторном рендеринге.
Как это происходит у «сигнальных»
В сигнальных фреймворках обновляются только реагирующие части интерфейса — без необходимости обхода дерева. Отсюда предварительный вывод: даже с компилятором Forget React всё ещё может работать медленнее библиотек, где сигналы используются по умолчанию.
Forget вырос в React Compiler
Это тот раздел книги, где проверка временем самая интересная: почти всё, что было «в оценке в Meta», уже случилось.
| Что обещала книга | Что произошло |
|---|---|
| сбылось Возможный выпуск Forget в open source «в ближайшем будущем» | Forget вышел под именем React Compiler: бета — октябрь 2024, стабильная v1.0 — 7 октября 2025 (React Conf). Открыт, обкатан на приложениях Meta, работает и в React, и в React Native |
| сбылось «Правила React» как условие работы компилятора | Правила стали проверяемыми: они въехали в eslint-plugin-react-hooks вместе с правилами самого компилятора. Ручные useMemo, useCallback и memo компилятор сохраняет — «раз вы их написали, значит, была причина» |
| сбылось Разработчик не должен думать о memo | Компилятор мемоизирует по анализу и эвристикам, в большинстве случаев не хуже, чем сделал бы человек. Для нового кода рекомендуется полагаться на компилятор, а useMemo/useCallback оставить как escape hatch для точного контроля |
| сбылось React не пойдёт по пути сигналов | Так и есть: TC39 proposal-signals — Stage 1, за ним Angular, Vue, Solid, Preact, Ember, Qwik, MobX. React выбрал компилятор и остался при vDOM |
| частично «Даже с Forget React может быть медленнее сигнальных» | Скепсис не опровергнут: модель осталась pull-based, обход дерева и сравнение пропсов никуда не ушли. Компилятор снял издержки ручной мемоизации, а не архитектурную разницу |
Куда React пошёл, кроме компилятора
Книга закрылась на Forget. За два года «будущее React» оказалось шире одного компилятора — и всё в том же направлении: не менять модель реактивности, а сокращать её издержки.
React 19
Серверные компоненты и server actions — в стабильном ядре. Работа переносится на сервер: клиенту едет меньше JavaScript. Это глава 9, которую мы уже разобрали.
React 19.2
<Activity mode> — участки UI как видимые, скрытые или приостановленные; useEffectEvent — событийная логика отдельно от реактивных эффектов; cacheSignal; частичный пререндеринг; улучшенный трекинг производительности.
React 19.2.6
Текущий стабильный релиз. React Compiler поддерживает 19.2 стабильно — компилятор и рантайм наконец идут в одном такте.
Пять вопросов главы 10
Вопросы из книги — по всей главе. Сначала обсуждаем, потом клик по вопросу раскрывает тезисный ответ.
Чем отличается модель реактивности в React, Vue, Svelte, Solid и Angular? Как это влияет на производительность и опыт разработки?
React — явный сеттер и повторный запуск функции компонента, дальше diff виртуального DOM (крупнозернисто). Vue — реактивные объекты плюс vDOM. Svelte — зависимости размечает компилятор, на выходе императивный код, обновляющий DOM напрямую. Solid — сигналы, компонент выполняется один раз, обновляются только читавшие узлы. Angular — обнаружение изменений в зонах и наблюдаемые объекты. Производительность: мелкозернистые модели не обходят дерево, React платит обходом и сравнением. Опыт разработки: React даёт предсказуемость и атомарность обновлений, сигнальные — меньше ручной оптимизации.
Опишите уникальный подход Qwik к максимизации производительности. Чем он отличается от остальных?
Возобновляемость вместо гидратации: состояние приложения сериализуется в HTML, и браузер продолжает работу с того места, где остановился сервер, вместо повторного выполнения кода на клиенте. Плюс приоритетная загрузка: JavaScript подтягивается по мере необходимости — по взаимодействию. Остальные платформы сначала гидратируют дерево целиком.
Каковы сильные и слабые стороны каждой библиотеки из главы? Как это влияет на выбор под конкретный проект?
Vue — мягкая кривая обучения и ненавязчивая реактивность; Angular — цельный «всё в комплекте» фреймворк с высокой ценой входа; Svelte — минимум рантайма, свой язык; Solid — самая точная реактивность при JSX-синтаксисе; Qwik — лучший первый экран, но самая молодая экосистема (это единственный недостаток, который книга называет прямо). React — крупнозернистая модель, зато крупнейшая экосистема и найм. Выбор — про команду, экосистему и профиль нагрузки, а не про «кто быстрее в бенчмарке».
React не является реактивным в традиционном смысле. Объясните это, сравнив с push-моделью Vue или Svelte.
В push-модели запись значения сама уведомляет подписчиков — изменение распространяется по системе. React работает pull: сеттер лишь планирует рендер, дальше функция компонента выполняется заново целиком и React сравнивает результат с прежним деревом. Формула v = f(s) это и фиксирует: представление есть функция состояния, но обновляется не само, а при повторном выполнении функции.
Что такое React Forget? Как он работает и как соотносится с сигналами?
Набор инструментов, похожий на линтер с --fix: применяет правила React и автоматически мемоизирует значения, которые не меняются за жизнь приложения, — компилируя React в более производительный React (в отличие от Svelte, компилирующего в императивный код). С сигналами не конкурирует по механике: сигналы живут вне иерархии компонентов и не требуют обхода дерева, а React обход сохраняет. Сегодня это React Compiler v1.0 — стабилен с октября 2025.
Глава 10 — в пяти строках
Этот доклад закрывает главу об альтернативах. Дальше — заключительная глава, где книга собирает всю картину целиком.
- Пять альтернатив по отдельности: Vue, Angular, Svelte, Solid и Qwik — каждая со своей философией, своими реактивными примитивами и своими компромиссами.
- Общие шаблоны: компонентная архитектура, декларативный синтаксис, механизм обновлений, методы жизненного цикла и современный JS-инструментарий — у всех.
- React не реактивен в традиционном смысле: грубый подход «изменение состояния → повторный рендеринг» вместо детализированной модели Vue и Svelte.
- Будущее React — не сигналы, а компилятор: Forget автоматически оптимизирует код, запоминая неизменные значения; сегодня это стабильный React Compiler.
- Вывод для практики: универсального подхода нет — понимание различий помогает выбирать инструмент и писать более эффективный код на любом из них.
Что далее
Антон Помазков
Артём Никифоров
Артём Никифоров
Артём Никифоров