Асинхронные серверные компоненты

Fresh — это подход к созданию веб-приложений, ориентированный на серверный рендеринг с использованием современного JavaScript и возможностей платформы Deno. Основная идея заключается в минимизации клиентской нагрузки и упрощении потоков данных, обеспечивая быстрый отклик страниц за счет генерации HTML на сервере и передачи его напрямую пользователю. Fresh работает без сложных сборщиков и виртуальных DOM, используя нативные возможности браузеров и сервера для обработки маршрутов, стилей и скриптов.

Архитектура Fresh

Архитектура Fresh строится вокруг нескольких ключевых компонентов:

  • Маршруты (Routes) — каждый путь приложения соответствует отдельному файлу в директории routes. Файлы обрабатываются сервером и возвращают готовый HTML.
  • Серверные компоненты (Server Components) — функции, выполняющиеся на сервере, возвращающие HTML. Они позволяют избегать передачи лишней логики на клиент.
  • Ограничение на клиентский код — в Fresh минимальное использование клиентских JavaScript-скриптов. Основная работа выполняется на сервере, а клиентский код добавляется только при необходимости интерактивности.
  • Автоматическая оптимизация — Fresh автоматически оптимизирует загрузку ресурсов, включая CSS и JS, подключая их только там, где они нужны.

Создание серверных компонентов

Серверные компоненты в Fresh — это функции, возвращающие разметку с данными. Основной синтаксис:

export default function Page({ params }) {
  return (
    <main>
      <h1>Привет, {params.name}!</h1>
      <p>Серверный рендеринг обеспечивает мгновенную загрузку страницы.</p>
    </main>
  );
}
  • params — объект с параметрами маршрута, доступный внутри компонента.
  • JSX-подобный синтаксис — используется для генерации HTML, но все вычисления происходят на сервере.
  • Отсутствие виртуального DOM — компоненты напрямую возвращают HTML, что снижает накладные расходы на рендеринг.

Асинхронные операции

Асинхронность в серверных компонентах реализуется с помощью стандартных JavaScript-промисов и async/await. Пример загрузки данных с внешнего API:

export default async function Page() {
  const response = await fetch("https://api.example.com/data");
  const data = await response.json();

  return (
    <section>
      <h1>Данные с API</h1>
      <ul>
        {data.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </section>
  );
}

Ключевые моменты:

  • Асинхронный код выполняется полностью на сервере.
  • HTML отправляется клиенту только после завершения всех операций.
  • Нет необходимости использовать клиентские state-менеджеры для первичной загрузки данных.

Интеграция с базой данных

Связка Fresh с базой данных позволяет строить динамические страницы без клиентских промежуточных шагов. Пример работы с PostgreSQL:

import { Client } from "https://deno.land/x/postgres/mod.ts";

const client = new Client({
  user: "user",
  password: "password",
  database: "testdb",
  hostname: "localhost",
  port: 5432,
});

await client.connect();

export default async function Page() {
  const result = await client.queryArray("SELECT id, name FROM users");
  
  return (
    <ul>
      {result.rows.map(([id, name]) => (
        <li key={id}>{name}</li>
      ))}
    </ul>
  );
}

Особенности работы:

  • Соединение с базой данных устанавливается на сервере.
  • Полученные данные сразу превращаются в HTML.
  • Исключается необходимость передачи необработанных данных на клиент.

Обработка маршрутов

Fresh использует файловую систему как основу маршрутизации:

  • Файл routes/index.tsx → маршрут /.
  • Файл routes/users/[id].tsx → динамический маршрут /users/:id.
  • Любой асинхронный код внутри файла выполняется до рендеринга страницы.

Маршруты могут содержать серверные компоненты, функции для загрузки данных (load), а также отдельные клиентские компоненты для интерактивных элементов.

Клиентская интерактивность

Хотя основной рендеринг происходит на сервере, Fresh поддерживает добавление интерактивных элементов:

/** @jsx h */
import { h } from "preact";
import { useState } from "preact/hooks";

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onCl ick={() => setCount(count + 1)}>
      Нажато {count} раз
    </button>
  );
}
  • Такие компоненты подключаются через директиву client:load или client:idle.
  • JavaScript загружается только при необходимости.
  • Серверный рендеринг обеспечивает быстрый первый показ страницы, а интерактивность добавляется постепенно.

Оптимизация и производительность

Fresh автоматически внедряет следующие механизмы оптимизации:

  • Минификация HTML, CSS и JS.
  • Разделение кода и lazy-loading для клиентских скриптов.
  • Кэширование серверного рендеринга для статических маршрутов.
  • Поддержка HTTP/2 и серверного push для ресурсов.

Эти меры позволяют снижать время загрузки страниц и уменьшать нагрузку на клиентские устройства.

Работа с состоянием

В серверных компонентах состояние хранится только на сервере в пределах запроса. Для многозадачных приложений можно комбинировать серверное состояние с локальным клиентским:

  • Серверный рендеринг обеспечивает первичный вывод данных.
  • Клиентский компонент добавляет интерактивность и локальное обновление состояния без повторного запроса на сервер.

Преимущества Fresh

  • Минимизация клиентской нагрузки.
  • Прямое взаимодействие с сервером и базой данных.
  • Полная интеграция с TypeScript.
  • Отсутствие сложных сборщиков и промежуточных слоев.
  • Высокая производительность за счет рендеринга на сервере и оптимизированной загрузки ресурсов.

Fresh подходит для построения современных веб-приложений с быстрым откликом, масштабируемой архитектурой и простой поддержкой асинхронных операций.