Advanced TypeScript patterns

В Fresh компоненты создаются как функции, возвращающие JSX. Для достижения строгой типизации используется TypeScript. Пропсы описываются интерфейсами или типами:

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

export default function Button({ label, onClick, disabled = false }: ButtonProps) {
  return (
    <button onCl ick={onClick} disabled={disabled}>
      {label}
    </button>
  );
}

Ключевой момент: использование опциональных свойств через ? позволяет задавать значения по умолчанию и сохранять строгую проверку типов при использовании компонента.


Generic-компоненты

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

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

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

Использование generics расширяет возможности переиспользования кода, не жертвуя безопасностью типов. Применение универсальных компонентов особенно важно при работе с API и динамическими данными.


Типы событий и синтетические события

Fresh поддерживает типизацию событий через стандартные типы DOM:

function InputField() {
  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    console.log(e.target.value);
  };

  return <input type="text" onCha nge={handleChange} />;
}

Использование точных типов событий (MouseEvent, KeyboardEvent, ChangeEvent) обеспечивает автодополнение и предотвращает ошибки при доступе к свойствам события.


Типизация состояния и хуков

Для стейт-переменных в Fresh через Deno и Preact используются хуки, которые можно типизировать явно:

import { useState } from "preact/hooks";

function Counter() {
  const [count, setCount] = useState<number>(0);

  return (
    <div>
      <p>{count}</p>
      <button onCl ick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Явная типизация стейта предотвращает неожиданные преобразования типов и ошибки при обновлении состояния.


Комбинированные типы и утилиты TypeScript

Использование Partial, Required, Pick, Omit позволяет создавать гибкие и безопасные типы для компонентов:

interface User {
  id: string;
  name: string;
  email?: string;
}

// Только обязательные поля
type MandatoryUser = Required<User>;

// Исключить поле email
type UserWithoutEmail = Omit<User, "email">;

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


Типизация fetch-запросов

При работе с API важно точно определять тип возвращаемых данных:

interface Post {
  id: number;
  title: string;
  body: string;
}

async function fetchPosts(): Promise<Post[]> {
  const res = await fetch("https://jsonplaceholder.typicode.com/posts");
  const data: Post[] = await res.json();
  return data;
}

Явное указание типа данных предотвращает ошибки при обработке ответа и обеспечивает поддержку автокомплита.


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

Fresh поддерживает создание контекста для управления состоянием приложения:

import { createContext } from "preact";
import { useContext } from "preact/hooks";

interface AuthContextValue {
  user: string | null;
  login: (username: string) => void;
}

const AuthContext = createContext<AuthContextValue | undefined>(undefined);

export function useAuth() {
  const context = useContext(AuthContext);
  if (!context) throw new Error("useAuth must be used within AuthProvider");
  return context;
}

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


Компоненты с условными пропсами

TypeScript позволяет создавать условные пропсы с помощью union-типов:

interface SuccessProps {
  status: "success";
  message: string;
}

interface ErrorProps {
  status: "error";
  code: number;
}

type AlertProps = SuccessProps | ErrorProps;

function Alert(props: AlertProps) {
  if (props.status === "success") {
    return <div>{props.message}</div>;
  }
  return <div>Error code: {props.code}</div>;
}

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


Внедрение типов для серверных обработчиков

Fresh использует функции handler для обработки HTTP-запросов. Типизация запроса и ответа повышает надежность:

import type { Handlers } from "$fresh/server.ts";

export const handler: Handlers<{ name: string }> = {
  GET(req, ctx) {
    return new Response(JSON.stringify({ name: "Fresh" }), {
      headers: { "Content-Type": "application/json" },
    });
  },
};

Типизация Handlers гарантирует, что структура ответа соответствует ожидаемым типам, предотвращая ошибки на клиентской стороне.


Декларация типов для сторонних библиотек

При использовании сторонних пакетов без типов можно создавать свои определения:

declare module "some-legacy-library" {
  export function legacyFunction(arg: string): number;
}

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


Эти паттерны типизации обеспечивают высокую надёжность, удобство сопровождения и масштабируемость приложений на Fresh с использованием TypeScript. Они позволяют строить комплексные компоненты, безопасно взаимодействовать с API и управлять состоянием с полной уверенностью в корректности типов.