TypeScript-типы и декларации

TypeScript в связке с библиотекой Iron используется не как дополнительный слой документации, а как полноценный механизм контроля структуры приложения на этапе компиляции. Основная цель типизации — обеспечить предсказуемость API компонентов, реактивных сущностей и внутренних утилит, исключая целый класс ошибок, связанных с несовпадением контрактов.

В Iron типы распределяются по нескольким уровням: компоненты, свойства, события, состояния и расширения модулей. Каждый уровень имеет собственные правила описания и взаимодействия.

Установка и подключение деклараций типов

В большинстве проектов типы подключаются автоматически через пакет библиотеки. Однако в случае ручной конфигурации TypeScript требуется убедиться, что компилятор видит декларации:

{
  "compilerOptions": {
    "strict": true,
    "moduleResolution": "node",
    "types": ["iron"]
  }
}

Если библиотека поставляется без встроенных типов, используются внешние декларации:

npm install @types/iron

или локальные файлы .d.ts, добавляемые в проект вручную.

Типизация компонентов

Компоненты в Iron описываются через обобщённые интерфейсы. Базовая структура компонента опирается на разделение входных параметров и возвращаемого представления.

interface ComponentProps {
  title: string;
  count?: number;
}

function Component(props: ComponentProps): JSX.Element {
  return /* ... */;
}

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

Декларация Props

Props в Iron не ограничиваются простыми объектами. Они могут включать вложенные структуры, функции обратного вызова и реактивные значения.

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

Допускается использование union-типов для создания гибких интерфейсов:

type Size = "small" | "medium" | "large";

interface AvatarProps {
  size: Size;
}

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

Типизация событий

События в Iron описываются через обобщённые сигнатуры. Это особенно важно при работе с DOM и кастомными событиями.

type EventHandler<T = Event> = (event: T) => void;

interface InputProps {
  onInput: EventHandler<InputEvent>;
  onFocus?: EventHandler<FocusEvent>;
}

Кастомные события требуют явного описания структуры payload:

interface UserEvent {
  id: number;
  action: string;
}

type UserEventHandler = (event: UserEvent) => void;

Реактивные состояния и типы

Реактивные сущности в Iron типизируются через обобщённые контейнеры состояния. Основной принцип — сохранение строгого соответствия между значением и его типом на протяжении всего жизненного цикла.

interface State<T> {
  value: T;
  set: (value: T) => void;
}

Использование:

const counter: State<number>;
const user: State<{ name: string; age: number }>;

Такой подход предотвращает неявные преобразования типов внутри реактивной системы.

Обобщённые компоненты

Обобщения позволяют создавать переиспользуемые компоненты без потери типобезопасности.

interface ListProps<T> {
  items: T[];
  render: (item: T) => string;
}

function List<T>(props: ListProps<T>) {
  return /* ... */;
}

При использовании тип выводится автоматически:

<List items={[1, 2, 3]} render={(n) => n.toString()} />;

Расширение типов через module augmentation

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

declare module "iron" {
  interface GlobalConfig {
    debug: boolean;
  }
}

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

Ambient declarations и глобальные типы

Для описания глобальных сущностей используются ambient declarations:

declare const __DEV__: boolean;

или расширение глобального пространства:

declare global {
  interface Window {
    ironApp: unknown;
  }
}

Такие объявления особенно важны при интеграции с legacy-кодом.

Декларационные файлы .d.ts

Файлы деклараций служат контрактом между JavaScript-реализацией и TypeScript-окружением. Структура типичного файла:

export interface IronConfig {
  root: string;
  strictMode: boolean;
}

export declare function createApp(config: IronConfig): void;

Ключевое требование — отсутствие реализации внутри .d.ts. Только описание структуры.

Типизация утилитных функций

Утилиты в Iron часто строятся на базе generics и conditional types.

type Maybe<T> = T | null;

function wrap<T>(value: T): Maybe<T> {
  return value;
}

Для сложных преобразований используются условные типы:

type IsString<T> = T extends string ? true : false;

Контекст и его типизация

Контекст в Iron описывается как строго типизированное хранилище зависимостей.

interface Context {
  theme: "light" | "dark";
  userId: number;
}

const AppContext: Context;

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

Интеграция с JavaScript-кодом

При подключении JavaScript-модулей используется декларация через any или постепенное уточнение типов.

declare module "legacy-lib" {
  const legacyFunction: (input: any) => any;
  export default legacyFunction;
}

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

Строгая типизация как основа архитектуры

Типовая система Iron не рассматривается как вспомогательный инструмент. Она формирует архитектурный контракт между слоями приложения. Любое нарушение типов фиксируется до выполнения кода, что позволяет переносить сложность из runtime в compile-time.

Использование интерфейсов, generics, деклараций и module augmentation формирует единый типовой слой, в котором каждая сущность имеет строго определённую структуру и поведение.