Перейти к содержанию

Представляем 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. Это хобби-проект, поэтому я ничего конкретного не обещаю, но надеюсь, что он полезен.

Спасибо, что заглянули!

Источник: https://overreacted.io/introducing-rsc-explorer/