Представляем RSC Explorer¶
Автор: Dan Abramov
За последние несколько недель, с момента раскрытия критической уязвимости безопасности в React Server Components (RSC), наблюдается большой интерес к протоколу RSC.
Протокол RSC — это формат, в котором React-деревья (и расширенный вариант JSON) сериализуются и десериализуются React. React предоставляет инструментарий чтения и записи для протокола RSC, которые версионируются и развиваются синхронно друг с другом.
Поскольку протокол RSC является деталью реализации React, он явно не документирован за пределами исходного кода. Преимущество такого подхода в том, что у React есть большая свобода для улучшения формата и добавления новых функций и оптимизаций.
Однако недостаток в том, что даже люди, активно создающие приложения с React Server Components, часто не имеют интуитивного понимания того, как это работает под капотом.
Несколько месяцев назад я написал Progressive JSON, чтобы объяснить некоторые идеи, используемые протоколом RSC. Хотя вам не «нужно» их знать для использования RSC, я думаю, это один из тех случаев, когда заглянуть под капот действительно интересно и поучительно.
Мне бы хотелось, чтобы обстоятельства возросшего интереса сейчас были другими, но в любом случае этот интерес вдохновил меня создать новый небольшой инструмент, чтобы показать, как это работает.
Я называю его RSC Explorer, и вы можете найти его по адресу https://rscexplorer.dev/.
Разумеется, он с открытым исходным кодом.
Hello World¶
«Показывай, а не рассказывай», как говорится. Ну вот он, встроенный пример.
Давайте начнём с Hello World:
Обратите внимание на жёлтую выделенную строку, которая содержит что-то загадочное. Если присмотреться, это <h1>Hello</h1>, представленный в виде фрагмента JSON. Эта строка — часть RSC-потока с сервера. Так React общается сам с собой по сети.
Теперь нажмите большую жёлтую кнопку «step»!
Обратите внимание, что <h1>Hello</h1> теперь появился справа. Это JSX, который клиент восстанавливает после чтения этой строки. Мы только что увидели, как простой фрагмент JSX — тег <h1>Hello</h1> — пересёк сеть и был воссоздан на другой стороне.
Ну, не совсем «пересёк сеть».
Одна классная особенность RSC Explorer в том, что это одностраничное приложение, то есть оно полностью работает в вашем браузере (точнее, серверная часть работает в воркере). Поэтому, если вы проверите вкладку Network, вы не увидите никаких запросов. Так что в некотором смысле это симуляция.
Тем не менее, RSC Explorer построен с использованием тех же самых пакетов, которые React предоставляет для чтения и записи протокола RSC, поэтому каждая строка вывода реальна.
Асинхронный компонент¶
Давайте попробуем что-то немного более интересное, чтобы увидеть стриминг в действии.
Возьмите этот пример и нажмите большую жёлтую кнопку «step» ровно два раза:
(Если вы сбились со счёта, нажмите «restart» слева, а затем «step» два раза снова.)
Посмотрите на верхнюю правую панель. Вы видите три чанка в формате протокола RSC (который, опять же, вам технически не нужно читать — и который меняется между версиями). Справа вы видите, что клиентский React восстановил на данный момент.
Обратите внимание на «дыру» в середине переданного дерева, визуализированную как пилюля «Pending».
По умолчанию React не показывал бы несогласованный UI с «дырами». Однако, поскольку вы объявили состояние загрузки с помощью <Suspense>, частично завершённый UI теперь может быть отображён (заметьте, что <h1> уже виден, но <Suspense> показывает fallback-контент, потому что <SlowComponent /> ещё не был передан).
Нажмите кнопку «step» ещё раз, и «дыра» будет заполнена.
Счётчик¶
До сих пор мы отправляли только данные клиенту; теперь давайте также отправим немного кода.
Давайте используем счётчик как классический пример.
Нажмите большую жёлтую кнопку «step» дважды:
Это просто старый добрый счётчик, ничего особо интересного.
Или есть?
Посмотрите на полезную нагрузку протокола. Её немного сложно читать, но обратите внимание, что мы не отправляем строку "Count: 0" или <button>-ы, или какой-либо HTML. Мы отправляем сам <Counter initialCount={0} /> — «виртуальный DOM». Конечно, его можно потом превратить в HTML, как и любой JSX, но это не обязательно.
Это как будто мы возвращаем React-деревья из API-маршрутов.
Обратите внимание, как ссылка на Counter становится ["client",[],"Counter"] в протоколе RSC, что означает «возьми экспорт Counter из модуля client». В реальном фреймворке это делается бандлером, поэтому RSC интегрируется с бандлерами. Если вы знакомы с webpack, это похоже на чтение из кеша require webpack. (На самом деле, именно так RSC Explorer это и реализует.)
Form Action¶
Мы только что видели, как сервер ссылается на фрагмент кода, предоставленный клиентом.
Теперь давайте посмотрим, как клиент ссылается на фрагмент кода, предоставленный сервером.
Здесь greet — это Server Action, доступный через 'use server' как эндпоинт. Он передаётся как проп клиентскому компоненту Form, который видит его как async функцию.
Нажмите большую жёлтую кнопку «step» три раза:
Теперь введите своё имя в панели Preview и нажмите «Greet». Отладчик RSC Explorer «приостановится» снова, показывая, что мы обратились к Server Action greet с запросом. Нажмите жёлтую кнопку «step», чтобы увидеть ответ, возвращённый клиенту.
Router Refresh¶
RSC часто преподаётся вместе с фреймворком, но это скрывает то, что происходит. Например, как фреймворк обновляет серверный контент? Как работает роутер?
RSC Explorer показывает RSC без фреймворка. Здесь нет router.refresh — но вы можете реализовать свой собственный Server Action refresh и компонент Router.
Нажимайте кнопку «step» повторно, чтобы получить весь начальный UI на экране:
Посмотрите на тикающий таймер. Обратите внимание, как серверный компонент ColorTimer передал случайный цвет клиентскому компоненту Timer. Опять же, сервер вернул <Timer color="hsl(96, 70%, 85%)" /> (или что-то подобное).
Теперь нажмите кнопку Refetch прямо под таймером.
Не вникая в код, «пошагово» пройдите через ответ сервера и посмотрите, что происходит. Вы должны увидеть постоянно тикающий Timer, получающий новые пропсы с сервера. Его фоновый цвет изменится, но его состояние сохранится!
В некотором смысле это похоже на повторную загрузку HTML с использованием чего-то вроде htmx, за исключением того, что это обычное React-обновление «виртуального DOM», поэтому оно не уничтожает состояние. Он просто получает новые пропсы… с сервера. Нажмите «Refetch» несколько раз и пошагово пройдите через это.
Если вы хотите посмотреть, как это работает под капотом, прокрутите вниз обе части — Server и Client. Вкратце, клиентский Router хранит Promise на серверный JSX, который возвращается renderPage(). Изначально renderPage() вызывается на сервере (для первого рендеринга), а позже вызывается с клиента (для обновлений).
Эта техника, в сочетании с сопоставлением URL и вложенностью, — это примерно то, как RSC-фреймворки обрабатывают маршрутизацию. Я думаю, это довольно крутой пример!
Что ещё?¶
Я сделал ещё несколько примеров для любопытных:
И, конечно, печально известный:
(Как и следовало ожидать, этот пример работает только на уязвимых версиях, поэтому вам нужно выбрать 19.2.0 в правом верхнем углу, чтобы он действительно заработал.)
Я бы хотел увидеть больше классных примеров RSC, созданных сообществом.
RSC Explorer позволяет встраивать фрагменты кода на другие страницы (как я сделал в этом посте) и создавать ссылки для обмена, если сам код не превышает лимит URL. Инструмент полностью клиентский, и я намерен сохранить его таким для простоты.
Вы можете свободно просматривать его исходный код на Tangled или GitHub. Это хобби-проект, поэтому я ничего конкретного не обещаю, но надеюсь, что он полезен.
Спасибо, что заглянули!