Дженерики в компонентах

Slim.js предоставляет мощный и легковесный подход к созданию веб-компонентов с реактивным обновлением данных. Одной из ключевых особенностей при разработке масштабируемых и переиспользуемых компонентов является использование дженериков, которые позволяют задавать тип данных, с которыми компонент будет работать.

Основы дженериков в Slim.js

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

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

import { Slim, html } from 'slim-js';

interface Item<T> {
  value: T;
}

class ListComponent<T> extends Slim {
  items: Item<T>[] = [];

  template() {
    return html`
      <ul>
        ${this.items.map(item => html`<li>${item.value}</li>`)}
      </ul>
    `;
  }
}

customElements.define('list-component', ListComponent);

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

Ограничения дженериков

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

interface HasId {
  id: number;
}

class IdListComponent<T extends HasId> extends Slim {
  items: T[] = [];

  template() {
    return html`
      <ul>
        ${this.items.map(item => html`<li>ID: ${item.id}</li>`)}
      </ul>
    `;
  }
}

customElements.define('id-list-component', IdListComponent);

Здесь T ограничен интерфейсом HasId, что гарантирует наличие свойства id у всех элементов списка.

Дженерики и реактивность

Slim.js использует реактивную привязку данных, поэтому дженерики легко интегрируются с системой обновления компонентов. Любое изменение в массиве items автоматически перерисует DOM:

const component = document.createElement('id-list-component') as IdListComponent<{ id: number; name: string }>;
component.items = [
  { id: 1, name: 'Первый' },
  { id: 2, name: 'Второй' }
];
document.body.appendChild(component);

// Добавление нового элемента
component.items.push({ id: 3, name: 'Третий' });
component.triggerUpdate(); // Slim.js отслеживает изменения

Использование дженериков гарантирует, что каждый элемент списка соответствует ожидаемой структуре, а TypeScript сразу сигнализирует о нарушениях типа.

Дженерики в пропсах и событиях

Компоненты могут принимать пропсы и эмитировать события с дженериками, что делает их максимально универсальными.

interface ButtonProps<T> {
  label: string;
  payload: T;
}

class GenericButton<T> extends Slim {
  props: ButtonProps<T>;

  handleClick() {
    this.emit('button-click', this.props.payload);
  }

  template() {
    return html`<button @click=${() => this.handleClick()}>${this.props.label}</button>`;
  }
}

customElements.define('generic-button', GenericButton);

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

Комбинирование дженериков с наследованием

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

class SelectComponent<T> extends Slim {
  options: T[] = [];
}

class UserSelectComponent extends SelectComponent<{ id: number; name: string }> {
  template() {
    return html`
      <select>
        ${this.options.map(user => html`<option value=${user.id}>${user.name}</option>`)}
      </select>
    `;
  }
}

Базовый класс SelectComponent может работать с любыми типами, а дочерний UserSelectComponent фиксирует конкретную структуру данных.

Преимущества использования дженериков

  • Гибкость и переиспользуемость: один компонент может работать с разными типами данных.
  • Безопасность типов: TypeScript предотвращает ошибки при работе с данными.
  • Совместимость с реактивностью Slim.js: изменения данных автоматически отражаются в DOM.
  • Упрощение наследования: базовые компоненты остаются универсальными, а специализированные — строго типизированными.

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