Около четырёх лет назад я прочитал лекцию React(продвинутый) в ШРИ. Там был большой разбор про то, как React работает под капотом.
Лекция неожиданно для меня хорошо зашла: я-то её планировал на аудиторию студентов ШРИ, а получилось 92к просмотров. До сих пор люди иногда пишут, что именно после неё у них щёлкнуло понимание React, и это меня сильно мотивирует.
Потом я надолго почти перестал писать про React.
Причина простая: я несколько лет преподавал React, не только в ШРИ, и в какой-то момент накопилась усталость скорее от преподавания, чем от самой темы.
А пока отдыхал, React взял и уехал вперёд.
Что осталось прежним
Главная база из той лекции не умерла.
React всё ещё не “просто меняет DOM”. Он строит work-in-progress дерево, сравнивает его с текущим деревом, готовит изменения и потом коммитит результат.
Ререндер всё ещё не равен “браузер перерисовал весь экран”.
key всё ещё влияет на то, считает ли React компонент “тем же самым” между рендерами.Ремаунт всё ещё может внезапно стереть локальный state.
Фаза коммита всё ещё не то место, где React может спокойно сказать: “ой, браузер занят, я продолжу позже”. Если дошли до применения изменений — надо применять.
То есть если вы тогда поняли идею
current tree и work-in-progress tree, это знание не превратилось в тыкву.Но сменился фокус
Раньше React часто объясняли через
Virtual DOM.Мол, React строит виртуальное дерево, сравнивает с прошлым, находит разницу и аккуратно обновляет настоящий DOM.
Как первая ступенька — норм. Как полная модель — уже слабовато.
Современный React всё меньше хочется объяснять через “сравнение деревьев” и всё больше через планирование работы.
Не просто:
— что изменилось;
— какой компонент ререндерится;
— сколько DOM-нод надо обновить.
А ещё:
— срочная это работа или нет;
— можно ли её прервать;
— можно ли показать старый UI, пока новый готовится;
— можно ли часть работы сделать на сервере;
— можно ли вообще не делать ручную мемоизацию, потому что её заберёт Compiler.
Вот это, по моему ощущению, главный сдвиг последних лет.
React стал не только библиотекой для описания UI. Он всё больше становится runtime-ом, который управляет тем, когда, где и с каким приоритетом этот UI должен появиться.
В старой лекции был классический пример:
родитель хранит
count, рядом лежит тяжёлый ChildVerySlow, нажимаем кнопку — и внезапно страдает вообще не тот компонент, который визуально поменялся.Решения тогда были понятные:
— изолировать state ближе к месту использования;
— не поднимать состояние без причины;
— следить за ремаунтами;
— аккуратно использовать
React.memo;— профилировать, а не гадать по ощущениям.
И это всё ещё хорошие советы.
Но теперь этого слоя знаний уже недостаточно.
React 18 принёс автоматический батчинг и конкурентный рендеринг. Появились
startTransition и useDeferredValue. Suspense перестал быть просто красивой обёрткой вокруг lazy. Server Components поменяли вопрос с “как быстрее отрендерить на клиенте” на “а должна ли эта работа вообще попасть на клиент”. React Compiler начал двигать нас в сторону мира, где часть ручного memo, useMemo и useCallback становится внутренней заботой React, а не обязательной ручной работой разработчика.Не магия. Не “теперь можно не думать”. Скорее наоборот: думать надо глубже и немного о другом.
Поэтому я хочу сделать серию постов про современный React через призму той старой лекции.
Не “что нового в React 18/19” списком из ченджлогов. А нормально, по-работяжьи:
— что из старой модели всё ещё держится;
— где старые объяснения стали слишком грубыми;
— как батчинг, транзишены и отложенный рендеринг меняют разговор про ререндеры;
— почему Suspense теперь не только про
React.lazy;— зачем нужны Server Components и почему
use client нельзя расставлять на автопилоте;— что React Compiler меняет в привычке мемоизировать всё руками.
Короче, будем снова залезать под капот React. Только уже не того React времён “хуки ещё выглядят свежей штукой”, а React, который пытается управлять всей жизнью интерфейса: от серверной работы до срочности обновлений на клиенте.