Generic компоненты

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

Определение generic компонента

Generic компонент — это компонент, который принимает тип-параметр. В JavaScript это реализуется через TypeScript, позволяя уточнять тип пропсов при использовании компонента. Синтаксис выглядит так:

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

function List<T>({ items, renderItem }: ListProps<T>) {
  return (
    <ul>
      {items.map((item, index) => (
        <li key={index}>{renderItem(item)}</li>
      ))}
    </ul>
  );
}
  • T — параметр типа, который задается при использовании компонента.
  • items — массив элементов типа T.
  • renderItem — функция, которая возвращает JSX для каждого элемента массива.

Использование generic компонента

Generic компонент можно применять для различных типов данных, сохраняя строгую типизацию:

interface User {
  id: number;
  name: string;
}

const users: User[] = [
  { id: 1, name: "Alice" },
  { id: 2, name: "Bob" },
];

<List<User>
  items={users}
  renderItem={(user) => <span>{user.name}</span>}
/>

Здесь T автоматически заменяется на User, и TypeScript гарантирует, что функция renderItem получает объект с правильной структурой.

Преимущества generic компонентов

  1. Переиспользуемость: один компонент может работать с любым типом данных.
  2. Безопасность типов: TypeScript проверяет соответствие типов на этапе компиляции, уменьшая риск ошибок во время выполнения.
  3. Упрощение поддержки кода: изменение типа данных не требует переписывания логики компонента, достаточно использовать другой тип-параметр.

Generic компоненты с несколькими параметрами типов

Иногда требуется использовать несколько типов. Fresh поддерживает это напрямую:

interface PairProps<T, U> {
  first: T;
  second: U;
  renderPair: (first: T, second: U) => JSX.Element;
}

function Pair<T, U>({ first, second, renderPair }: PairProps<T, U>) {
  return <div>{renderPair(first, second)}</div>;
}

Пример использования:

<Pair<number, string>
  first={42}
  second={"Answer"}
  renderPair={(num, str) => <span>{num} - {str}</span>}
/>

Ограничения generic компонентов

TypeScript позволяет накладывать ограничения на типы параметров через extends:

interface Identifiable {
  id: number;
}

interface IdentifiableListProps<T extends Identifiable> {
  items: T[];
}

function IdentifiableList<T extends Identifiable>({ items }: IdentifiableListProps<T>) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{JSON.stringify(item)}</li>
      ))}
    </ul>
  );
}

Такой подход гарантирует, что все объекты имеют поле id, что делает компонент более строгим и безопасным.

Generic компоненты и JSX

Fresh использует JSX для описания структуры компонентов. Generic компоненты интегрируются полностью с JSX, включая вложенные компоненты и условный рендеринг:

function Card<T>({ data, renderContent }: { data: T; renderContent: (data: T) => JSX.Element }) {
  return <div class="card">{renderContent(data)}</div>;
}

<Card data={{ title: "Hello", description: "Fresh Framework" }} renderContent={(d) => (
  <>
    <h1>{d.title}</h1>
    <p>{d.description}</p>
  </>
)}/>

Тип T в этом примере позволяет компоненту работать с любыми объектами, обеспечивая максимальную гибкость без потери типизации.

Резюме по generic компонентам

  • Generic компоненты повышают гибкость и повторное использование кода.
  • Они интегрируются с JSX и всей экосистемой Fresh.
  • Строгая типизация через TypeScript делает код более безопасным и понятным.
  • Поддерживаются как одиночные, так и множественные параметры типов, а также ограничения типов через extends.

Использование generic компонентов является фундаментальной практикой при построении масштабируемых приложений на Fresh, позволяя создавать мощные и универсальные UI-структуры.