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

Atomico — это современная библиотека для создания веб-компонентов на базе JavaScript, которая использует реактивный подход и функциональные компоненты. Одним из важных инструментов, повышающих гибкость компонентов, являются дженерики. Они позволяют задавать типы данных, с которыми будет работать компонент, делая его более универсальным и безопасным в типизации.


Параметры типов в функциях компонентов

Компоненты в Atomico создаются функцией component(), которая возвращает объект с описанием веб-компонента. Дженерики позволяют задавать типы props, events и ref, что делает компоненты строго типизированными.

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

import { component, html, Props } from "atomico";

type ButtonProps<T = string> = {
    label: T;
    disabled?: boolean;
};

function Button<T extends string = string>({ label, disabled }: Props<ButtonProps<T>>) {
    return html`
        <button ?disabled=${disabled}>${label}</button>
    `;
}

customElements.define("my-button", component(Button));

В этом примере T задаёт тип для label. Если компонент используется без указания типа, по умолчанию это string. Такой подход позволяет создавать универсальные кнопки, которые могут отображать текст или другие типы данных.


Дженерики для событий

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

type SelectEvent<T> = CustomEvent<{ value: T }>;

function Select<T>({ options }: Props<{ options: T[] }>) {
    function handleChange(event: Event) {
        const value = (event.target as HTMLSelectElement).value as unknown as T;
        dispatch<SelectEvent<T>>(element, "change", { value });
    }

    return html`
        <SELECT onCha nge=${handleChange}>
            ${options.map(option => html`<option value=${option}>${option}</option>`)}
        </select>
    `;
}

Здесь Select<T> может работать с любыми типами данных, а событие change строго типизировано. TypeScript будет проверять, что value соответствует типу T.


Дженерики и ref

С помощью дженериков можно типизировать ref — ссылку на DOM-элемент или компонент. Это позволяет безопасно работать с элементами внутри функционального компонента.

import { useRef } FROM "atomico";

function Input<T extends HTMLInputElement>() {
    const inputRef = useRef<T>();

    function focusInput() {
        inputRef.current?.focus();
    }

    return html`
        <input ref=${inputRef} />
        <button onCl ick=${focusInput}>Focus</button>
    `;
}

В этом примере inputRef строго типизирован как HTMLInputElement, что исключает ошибки при работе с DOM-методами.


Композиция дженериков

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

type Field<T> = {
    name: string;
    value: T;
};

type FormProps<TFields extends Record<string, any>> = {
    fields: { [K in keyof TFields]: Field<TFields[K]> };
};

function Form<TFields>({ fields }: Props<FormProps<TFields>>) {
    function handleSubmit() {
        const values: TFields = {} as TFields;
        Object.keys(fields).forEach(key => {
            values[key as keyof TFields] = fields[key as keyof TFields].value;
        });
        dispatch<CustomEvent<TFields>>(element, "submit", values);
    }

    return html`
        <form onSub mit=${handleSubmit}>
            ${Object.values(fields).map(
                field => html`<input name=${field.name} value=${field.value} />`
            )}
            <button type="submit">Submit</button>
        </form>
    `;
}

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


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

  • Дженерики должны иметь ограничение extends, если требуется использовать специфические методы или свойства типов.
  • При передаче нескольких дженериков важно явно указывать их порядок, чтобы избежать конфликтов типов.
  • Не все встроенные функции Atomico автоматически выводят дженерики, поэтому иногда требуется явное указание типов для Props, Ref и событий.

Практические советы

  • Использовать дженерики для компонентов с повторяющимися шаблонами данных.
  • Типизировать все пользовательские события через дженерики для строгой проверки.
  • Комбинировать дженерики с mapped types для универсальных форм и коллекций.
  • Явно задавать типы при работе с DOM-ссылками (ref), чтобы избежать ошибок.

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