Преимущества
Fluent React: Build Fast, Performant, and Intuitive Web Applications
Глубокое погружение во внутреннее устройство React: JSX и продвинутые паттерны, виртуальный DOM и реконциляция, серверный рендеринг, конкурентный режим и серверные компоненты.
Программа вечера
Антон Помазков
Антон Помазков
Артём Никифоров
Артём Никифоров
Артём Никифоров
Предыдущая глава: Зачем нужен фреймворк?
Абстрагирование
Фреймворк скрывает сложные детали: маршрутизацию, гидратацию, сборку, кэширование. Разработчик сосредотачивается на бизнес-логике.
Соглашения
Единые правила структуры файлов, именования и потоков данных ускоряют командную работу и снижают когнитивную нагрузку.
Комплексные решения
Фреймворк даёт готовый набор инструментов: рендеринг, маршрутизацию, оптимизацию ассетов, работу с данными.
Производительность
Правильно выбранный фреймворк положительно влияет на скорость загрузки и отклика приложения.
Серверные компоненты React (RSC)
В прошлой главе их лишь немного упомянули — сегодня разберём подробнее: что это за компоненты, где они выполняются и что попадает в клиентский бандл.
Нажмите на блок — он раскроется, остальные свернутся до иконок
Особенности RSC
«Запускаются» на сервере
Функция компонента вызывается и выполняется в серверной среде — во время сборки или при запросе. В браузере она не выполняется никогда.
Исключаются из клиентского пакета JavaScript
JS-код RSC не попадает в клиентский бандл — пользователь скачивает только результат его работы.
Преимущество RSC над клиентскими
Серверные компоненты могут стартовать
во время сборки (build time)
Build time — этап сборки приложения: код выполняется один
раз, ещё до того как страницу запросил первый пользователь.
Результат «вшивается» в готовую разметку, поэтому на запрос
ничего пересчитывать не нужно. Противоположность —
request time, когда компонент выполняется
на каждый запрос.
— и тогда им доступны операции, которых в браузере просто нет.
Чтение из файловой системы
Импорт Markdown-файлов, локальных JSON, конфигураций. В браузере прямой доступ к файловой системе невозможен или сильно ограничен — на сервере при сборке это естественная операция.
import fs from "node:fs/promises"; const Post = async () => { const md = await fs.readFile( "posts/hello.md", "utf8" ); return <article>{md}</article>; };
Извлечение статического содержимого
Данные из CMS, статических API, внешних источников — на этапе сборки. Контент «фиксируется» заранее, и его не приходится запрашивать при каждом обращении пользователя.
const Posts = async () => { const posts = await fetch( "https://cms.example/api/posts" ).then((r) => r.json()); return <List items={posts} />; };
Доступ к уровню данных
Серверные компоненты обращаются напрямую к базам данных, внутренним сервисам и ORM-слою. Для простых операций чтения отдельный API-слой становится не нужен.
import { db } from "./db"; const Users = async () => { const users = await db .select().from(usersTable); return <Table rows={users} />; };
Как работает RSC?
Создаём RSC
RSC — это особый тип
компонента React
Компонент React — это обычная JavaScript-функция,
которая принимает пропсы и возвращает элемент React:
описание того, каким должен быть UI. Не HTML-строку
и не DOM-узел. JSX внутри компилируется в вызовы
React.createElement.
, который выполняется только на сервере.
// минимальный серверный компонент const Greeting = async () => { return <div>Hi!</div>; };
Вызываем RSC на сервере
Функция компонента вызывается и выполняется в серверной среде — во время сборки (build time) или при запросе (request time). В браузере она не выполняется никогда.
// на сервере, с await: компонент может быть асинхронным const element = await Greeting();
RSC возвращает элемент React
Серверный компонент возвращает
элемент React
Элемент — это JSON-подобный объект, у которого есть
свойства: $$typeof, type
('div', 'span' или ссылка
на другой компонент), props вместе
с children и вложенными элементами.
— ровно то же, что и клиентский компонент.
// что вернул наш Greeting { $$typeof: Symbol(react.element), type: "div", props: { children: "Hi!" } }
Сериализуем RSC в payload
Дерево элементов — обычный объект, поэтому его можно
превратить в поток для передачи. Мешает только
$$typeof: его значение — Symbol,
а символы не сериализуются, поэтому маркер заменяют
строкой.
// сериализованный элемент '{"$$typeof":"react.element", "type":"div", "props":{"children":"Hi!"}}'
Отправляем payload по сети
Клиент получает результат работы компонента, а не его код: сам компонент, его импорты и зависимости остаются на сервере и в бандл не попадают.
// ответ сервера — поток, а не бандл res.end(serializedTree);
Клиент рендерит результат RSC
React на клиенте восстанавливает дерево (символ-маркер возвращается на место) и рендерит его. Клиентские компоненты при этом остаются привычными — с хуками, состоянием и обработчиками событий.
// на клиенте const element = JSON.parse(payload, reviver); root.render(element);
Преимущества RSC
Преимущество 01
Предсказуемая производительность
Предсказуемость
- код можно профилировать и оптимизировать;
- можно гарантировать уровень сервиса (SLA);
- нет деградации на слабых устройствах;
- масштабирование планируется заранее.
Мощности, которые мы контролируем
- сколько CPU и RAM;
- какая версия Node.js или среды выполнения;
- какие ограничения и квоты заданы;
- как масштабируется нагрузка.
Не считаем на чужих устройствах
Парк устройств разнороден: от мощных десктопов до старых смартфонов с медленным CPU и малой памятью. Тяжёлый код на клиенте — это вычисления, которые мы не контролируем.
Преимущество 02
Безопасность серверных сред и защита секретов
Защищённые серверные среды
Дата-центры, приватные сети, переменные окружения, секреты в CI/CD — среда, доступ к которой контролируем мы, а не конечный пользователь.
Безопасные операции
- обращение к базам данных с учётными данными;
- вызовы внутренних API с секретными ключами;
- проверка прав доступа;
- работа с платёжными системами и токенами.
Секреты не утекают в браузер
На клиенте те же операции означали бы, что любой может:
- посмотреть исходный код бандла;
- перехватить сетевые запросы;
- извлечь токены и ключи.
Важное ограничение
Преимущество работает, пока секрет не передан клиенту сами. Отдали секретное значение пропсом в клиентский компонент ('use client') — оно сериализуется и уедет в браузер. Правило: секреты живут только внутри серверных компонентов.
Преимущество 03
Асинхронность серверных компонентов
В клиентском React компонент не может быть async-функцией: React ждёт синхронный возврат JSX, а данные приходят через хуки (useEffect, Suspense, use()). У серверных компонентов этого ограничения нет — можно объявить async и напрямую await-ить загрузку.
Почему так можно
Сервер контролирует момент отправки ответа, поэтому он может:
- начать выполнение асинхронного компонента;
- дождаться разрешения всех промисов;
- и только потом сериализовать результат и ответить.
В браузере это дороже
Там React не может «заблокировать» рендеринг в ожидании данных без сложных механизмов. На сервере ожидание — просто задержка ответа, которую мы полностью контролируем.