TypeScript

SkateJS — это легковесная библиотека для создания веб-компонентов, построенная на стандартах Custom Elements. Полная интеграция с TypeScript позволяет не только обеспечить строгую типизацию, но и использовать все возможности современных IDE: автодополнение, проверку типов и безопасные рефакторинги.


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

Основная единица SkateJS — компонент, создаваемый через функцию define. В TypeScript важным аспектом является корректная типизация пропсов и слотов. Для этого используется generic-параметр при вызове withComponent или define.

Пример базового компонента с типами:

import { define, Component, prop } from 'skatejs';

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

class MyComponent extends Component<MyComponentProps> {
  static props = {
    title: prop.string(),
    count: prop.number()
  };

  render() {
    return (
      <div>
        <h1>{this.title}</h1>
        <p>Count: {this.count}</p>
      </div>
    );
  }
}

define('my-component', MyComponent);

Ключевые моменты:

  • Component<MyComponentProps> задаёт интерфейс пропсов.
  • prop.string() и prop.number() обеспечивают соответствие типов в DOM-атрибутах.
  • Типы пропсов позволяют TypeScript проверять корректность использования компонента при его вставке в JSX или при программном обновлении атрибутов.

Наследование и расширение типов

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

interface BaseProps {
  active: boolean;
}

class BaseComponent<P extends BaseProps> extends Component<P> {
  toggle() {
    this.active = !this.active;
  }
}

interface ButtonProps extends BaseProps {
  label: string;
}

class ButtonComponent extends BaseComponent<ButtonProps> {
  static props = {
    ...BaseComponent.props,
    label: prop.string()
  };

  render() {
    return <button>{this.label}</button>;
  }
}

define('my-button', ButtonComponent);

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

  • Использование дженериков <P extends BaseProps> позволяет наследовать пропсы.
  • Расширение props через ...BaseComponent.props сохраняет корректную типизацию.
  • Методы базового класса доступны в наследнике с полной проверкой типов.

Работа с событиями и пользовательскими методами

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

interface EventProps {
  onIncrement?: (value: number) => void;
}

class CounterComponent extends Component<EventProps> {
  static props = {
    onIncrement: prop.callback()
  };

  private count = 0;

  increment() {
    this.count++;
    if (this.onIncrement) {
      this.onIncrement(this.count);
    }
  }

  render() {
    return (
      <button onCl ick={() => this.increment()}>Increment</button>
    );
  }
}

define('counter-component', CounterComponent);

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

  • prop.callback() обеспечивает проверку типа функции.
  • При вызове this.onIncrement TypeScript проверяет сигнатуру, предотвращая ошибки вызова.
  • Прямое использование событий через CustomEvent также типизируется через интерфейсы:
this.dispatchEvent(new CustomEvent<{ value: number }>('increment', { detail: { value: this.count } }));

JSX и SkateJS с TypeScript

SkateJS поддерживает JSX, что делает интеграцию с TypeScript естественной. Для корректной работы необходимо настроить tsconfig.json с "jsx": "react" или "jsx": "react-jsx" и объявить типы для элементов SkateJS:

declare namespace JSX {
  interface IntrinsicElements {
    'my-component': { title: string; count: number };
    'counter-component': { onIncrement?: (value: number) => void };
  }
}

После этого использование компонентов становится полностью типобезопасным:

<my-component title="Пример" count={5}></my-component>
<counter-component onIncrem ent={(val) => console.log(val)}></counter-component>

Пропсы с дефолтными значениями

SkateJS позволяет задавать дефолтные значения пропсов через defaultValue. В TypeScript это можно типизировать через Partial:

interface BadgeProps {
  label: string;
  color?: string;
}

class BadgeComponent extends Component<BadgeProps> {
  static props = {
    label: prop.string(),
    color: prop.string({ defaultValue: 'blue' })
  };

  render() {
    return <span style={{ color: this.color }}>{this.label}</span>;
  }
}

define('my-badge', BadgeComponent);
  • color становится необязательным для пользователя, но всегда имеет значение внутри компонента.
  • Типизация через интерфейс и defaultValue предотвращает ошибки undefined.

Итерации и слоты

SkateJS поддерживает стандартные слоты для распределения контента. В TypeScript слоты можно типизировать с помощью generic-параметров:

interface CardProps {
  title: string;
}

class CardComponent extends Component<CardProps> {
  static props = {
    title: prop.string()
  };

  render() {
    return (
      <div class="card">
        <h2>{this.title}</h2>
        <slot></slot>
      </div>
    );
  }
}

define('my-card', CardComponent);
  • Любой контент внутри <my-card> автоматически рендерится в <slot>.
  • TypeScript позволяет контролировать пропсы компонента, но содержимое слота остаётся динамическим, что соответствует стандартам Web Components.

Интеграция с внешними библиотеками

TypeScript в SkateJS облегчает интеграцию с React, Lit или другими библиотеками, обеспечивая корректную типизацию свойств и событий:

const element = document.createElement('counter-component') as CounterComponent;
element.onIncrem ent = (value) => console.log('Incremented to', value);
document.body.appendChild(element);
  • Использование кастов as CounterComponent позволяет IDE предлагать автодополнение для методов и пропсов.
  • Сочетание типизации и SkateJS гарантирует безопасное взаимодействие с DOM без потери удобства.

SkateJS с TypeScript создаёт полноценную среду для построения веб-компонентов с строгой проверкой типов, безопасными событиями и интеграцией с современными инструментами фронтенда. Такой подход минимизирует ошибки на этапе компиляции и улучшает поддержку кода при больших проектах.