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

Slim.js — это легковесная библиотека для создания веб-компонентов с минимальным синтаксисом и высокой производительностью. В TypeScript интеграция Slim.js требует точного определения типов для компонентов, их свойств и событий, особенно если библиотека не предоставляет собственных деклараций типов. Это позволяет использовать автодополнение, проверку типов и безопасное взаимодействие с компонентами.

Типизация в Slim.js строится вокруг следующих ключевых элементов: компоненты, свойства (props) и события.


Определение типов для компонентов

Каждый компонент Slim.js реализуется как класс, наследующийся от Slim или создаваемый через Slim.tag(). В TypeScript можно задать интерфейс для свойств и методов компонента:

import { Slim } from 'slim-js';

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

class MyComponent extends Slim<MyComponentProps> {
  constructor() {
    super();
    this.title = 'Пример';
    this.count = 0;
  }

  increment() {
    this.count! += 1;
  }
}

customElements.define('my-component', MyComponent);

Ключевой момент — указание дженерика <MyComponentProps>, чтобы TypeScript понимал структуру свойств. Свойства с ? считаются необязательными.


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

Если библиотека не имеет собственных .d.ts файлов, можно создать их вручную. Для Slim.js это обычно включает три шага:

  1. Создание файла slim.d.ts в проекте или папке types.
  2. Определение интерфейсов для компонентов и их свойств.
  3. Экспорт типов и глобальное расширение пространства имён для HTMLElementTagNameMap, чтобы компоненты корректно распознавались как HTML-элементы.

Пример декларации:

declare module 'slim-js' {
  export class Slim<T = any> extends HTMLElement {
    props: T;
    update(): void;
  }

  export function tag(name: string, component: typeof Slim): void;
}

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

declare global {
  interface HTMLElementTagNameMap {
    'my-component': MyComponent;
  }
}

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


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

Slim.js поддерживает пользовательские события через стандартный API CustomEvent. Чтобы задать тип события, создаётся интерфейс и используем дженерики при вызове dispatchEvent:

interface CounterEventDetail {
  value: number;
}

class CounterComponent extends Slim<{ count: number }> {
  increment() {
    this.props.count += 1;
    this.dispatchEvent(new CustomEvent<CounterEventDetail>('countChanged', {
      detail: { value: this.props.count },
      bubbles: true
    }));
  }
}

document.querySelector<CounterComponent>('counter-component')?.addEventListener('countChanged', (event) => {
  console.log(event.detail.value);
});

Использование дженериков для CustomEvent позволяет TypeScript проверять доступ к полю detail и его структуру.


Автоматическая генерация типов

При работе с несколькими компонентами удобно создавать автоматические декларации типов через утилиты. Можно написать скрипт на Node.js, который анализирует компоненты и генерирует соответствующие интерфейсы TypeScript. Это особенно полезно для проектов с динамическими свойствами и большим количеством элементов.


Типизация атрибутов и слотов

Свойства компонента часто синхронизируются с HTML-атрибутами. Для строгой типизации создаются интерфейсы атрибутов:

interface ButtonProps {
  label: string;
  disabled?: boolean;
}

class ButtonComponent extends Slim<ButtonProps> {
  connectedCallback() {
    if (this.hasAttribute('disabled')) this.props.disabled = true;
  }
}

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

type SlotContent = HTMLElement | string;

class CardComponent extends Slim<{ header?: SlotContent; body?: SlotContent }> {}

Использование деклараций в больших проектах

Для масштабных проектов создаётся отдельная папка types/slim/ с файлами .d.ts для каждого компонента. Поддерживается структура модулей, чтобы импорты оставались чистыми:

types/
  slim/
    index.d.ts
    button.d.ts
    card.d.ts

В index.d.ts агрегируются все экспортируемые типы:

export * from './button';
export * from './card';

TypeScript автоматически подхватывает эти типы при подключении Slim.js.


Рекомендации по совместимости

  • Всегда указывать дженерик <Props> при наследовании от Slim, чтобы избежать any.
  • Расширять HTMLElementTagNameMap для корректной работы querySelector и автодополнения.
  • Для событий использовать дженерики CustomEvent<T> и явные интерфейсы деталей события.
  • Для сложных компонентов с динамическими свойствами создавать отдельные декларации .d.ts и подключать их через types в tsconfig.json.

Эти подходы обеспечивают строгую типизацию, автодополнение и предотвращают ошибки на этапе компиляции, делая разработку на Slim.js в TypeScript безопасной и удобной.