Component patterns

Fresh — современный фреймворк для разработки веб-приложений на Deno, который ориентирован на минимализм и рендеринг на сервере. Компоненты во Fresh выполняют роль строительных блоков интерфейса, обеспечивая повторное использование кода, разделение логики и представления, а также интеграцию с серверной частью приложения. Рассмотрим ключевые паттерны работы с компонентами.


Функциональные компоненты

В Fresh компоненты создаются как функции, которые возвращают JSX. Они могут принимать props для передачи данных и управлять отображением интерфейса. Такой подход позволяет легко комбинировать компоненты и создавать динамические интерфейсы.

interface ButtonProps {
  label: string;
  onClick?: () => void;
}

export default function Button({ label, onClick }: ButtonProps) {
  return (
    <button onCl ick={onClick} class="px-4 py-2 bg-blue-500 text-white rounded">
      {label}
    </button>
  );
}

Особенности:

  • JSX-компоненты рендерятся на сервере по умолчанию.
  • Любые пропсы строго типизированы через TypeScript.
  • Локальные обработчики событий передаются как функции.

Комбинирование компонентов

Компоненты легко вкладываются друг в друга, образуя сложные интерфейсы. Передача пропсов позволяет создавать универсальные элементы.

interface CardProps {
  title: string;
  description: string;
}

export default function Card({ title, description }: CardProps) {
  return (
    <div class="p-4 border rounded shadow-md">
      <h2 class="text-xl font-bold">{title}</h2>
      <p>{description}</p>
    </div>
  );
}

export function App() {
  return (
    <div class="space-y-4">
      <Card title="Card 1" description="Описание первой карточки" />
      <Card title="Card 2" description="Описание второй карточки" />
    </div>
  );
}

Преимущества вложенных компонентов:

  • Легкая масштабируемость интерфейса.
  • Повторное использование одинаковой структуры с различными данными.
  • Упрощение тестирования и поддержки кода.

Состояние компонентов

Fresh поддерживает концепцию островов интерактивности (islands), где части страницы могут быть интерактивными без полной нагрузки на клиент. Состояние компонентов можно хранить с помощью хука useState из Preact, так как Fresh использует Preact для клиентской интерактивности.

import { useState } from "preact/hooks";

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

  return (
    <div>
      <p>Счётчик: {count}</p>
      <button onCl ick={() => setCount(count + 1)}>Увеличить</button>
    </div>
  );
}

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

  • Состояние существует только на клиенте.
  • Инициализация происходит на сервере, рендер на клиенте активируется по необходимости.
  • Позволяет создавать интерактивные виджеты без полной SPA-структуры.

Паттерн контейнеров и презентационных компонентов

Разделение логики и представления повышает читаемость и переиспользуемость кода. В Fresh это реализуется через разделение компонентов на контейнеры (логика, загрузка данных) и презентационные компоненты (рендер UI).

// Контейнер
import CardList from "./CardList.tsx";

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

  return <CardList cards={cards} />;
}

// Презентационный компонент
interface CardListProps {
  cards: { title: string; description: string }[];
}

export default function CardList({ cards }: CardListProps) {
  return (
    <div class="grid grid-cols-3 gap-4">
      {cards.map(card => (
        <div key={card.title} class="p-4 border rounded shadow">
          <h3>{card.title}</h3>
          <p>{card.description}</p>
        </div>
      ))}
    </div>
  );
}

Преимущества подхода:

  • Контейнер отвечает за загрузку и подготовку данных.
  • Презентационный компонент только отображает данные.
  • Облегчает тестирование компонентов UI отдельно от бизнес-логики.

Паттерн render props

Render props позволяют передавать функцию для кастомного рендеринга части компонента, обеспечивая максимальную гибкость. В Fresh этот паттерн часто применяется для сложных UI-элементов и динамических интерфейсов.

interface ListProps<T> {
  items: T[];
  renderItem: (item: T) => JSX.Element;
}

export function List<T>({ items, renderItem }: ListProps<T>) {
  return <ul>{items.map(item => <li>{renderItem(item)}</li>)}</ul>;
}

// Использование
<List
  items={[1, 2, 3]}
  renderItem={num => <span>Элемент {num}</span>}
/>

Особенности:

  • Позволяет полностью контролировать рендер отдельных элементов.
  • Сохраняется чистота и повторное использование компонентов.
  • Часто комбинируется с композиционными паттернами.

Компоненты с динамическими импортами

Fresh поддерживает динамический импорт компонентов, что полезно для условного рендеринга и оптимизации загрузки.

let DynamicComponent;

if (someCondition) {
  DynamicComponent = (await import("./SpecialComponent.tsx")).default;
}

export default function Page() {
  return (
    <div>
      {DynamicComponent ? <DynamicComponent /> : <p>Обычное содержимое</p>}
    </div>
  );
}

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

  • Сокращение времени первоначальной загрузки страницы.
  • Возможность загружать компоненты только по необходимости.
  • Улучшение производительности крупных приложений.

Итоговое понимание паттернов

Основные паттерны компонентов во Fresh обеспечивают:

  • Четкое разделение логики и представления.
  • Возможность повторного использования и композиции.
  • Оптимизацию клиентской интерактивности через острова.
  • Гибкость рендеринга с использованием render props и динамических импортов.

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