Спецификация Custom Elements

Atomico — это библиотека для создания Web Components, которая использует стандарт Custom Elements. В основе Atomico лежит подход, позволяющий описывать компоненты как функции с декларативным рендером и реактивным состоянием. Каждое определение компонента в Atomico превращается в Custom Element, автоматически регистрируемый в DOM.

Создание компонента

Компонент создаётся с помощью функции component:

import { component, html } from "atomico";

function MyButton({ label, onClick }) {
    return html`
        <button oncl ick=${onClick}>${label}</button>
    `;
}

customElements.define("my-button", component(MyButton));
  • component(MyButton) оборачивает функцию в объект, совместимый со стандартом Custom Elements.
  • customElements.define регистрирует элемент под именем "my-button".

После регистрации <my-button> становится полноценным HTML-тегом, доступным в любом месте документа.

Атрибуты и свойства

В Atomico свойства компонента связываются с props. Любое свойство, переданное через атрибут HTML, автоматически конвертируется в соответствующее значение:

<MyButton label="Нажми меня" />

Для явного объявления свойств используется props:

MyButton.props = {
    label: String,
    disabled: Boolean
};
  • Atomico автоматически наблюдает за изменениями этих свойств.
  • При обновлении свойства происходит реактивный ререндер компонента.
  • Типы помогают валидации и обеспечивают корректное преобразование значений атрибутов HTML в свойства компонента.

События

Custom Elements в Atomico поддерживают кастомные события через emit:

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

function MyButton({ label }) {
    const handleClick = useEvent("press");

    return html`<button oncl ick=${handleClick}>${label}</button>`;
}

MyButton.props = {
    label: String
};

customElements.define("my-button", component(MyButton));
  • useEvent("press") создаёт функцию, которая эмитит событие press наружу.
  • События можно слушать стандартным способом: <my-button @press="handler">.

Shadow DOM и стили

Atomico по умолчанию использует Shadow DOM, изолируя стили и разметку компонента. Это позволяет не загрязнять глобальный CSS и создавать полностью независимые элементы:

import { css } from "atomico";

const styles = css`
    button {
        padding: 10px;
        border-radius: 5px;
        background: #0078d7;
        color: white;
        border: none;
    }
`;

function MyButton({ label }) {
    return html`
        <button>${label}</button>
        ${styles}
    `;
}
  • Стили автоматически инкапсулируются в Shadow DOM.
  • Можно использовать CSS-переменные для динамического изменения внешнего вида.

Жизненный цикл компонента

Atomico предоставляет хуки, соответствующие стандартному жизненному циклу Custom Elements:

  • useEffect — аналог connectedCallback и disconnectedCallback.
  • useRef — для доступа к DOM внутри компонента.
  • useState — для реактивного управления локальным состоянием.

Пример с эффектом:

import { component, html, useEffect, useState } from "atomico";

function Counter() {
    const [count, setCount] = useState(0);

    useEffect(() => {
        const interval = setInterval(() => setCount(c => c + 1), 1000);
        return () => clearInterval(interval);
    }, []);

    return html`<div>Счёт: ${count}</div>`;
}

customElements.define("my-counter", component(Counter));
  • Хук useEffect принимает функцию с возвратом функции очистки.
  • Этот подход полностью совместим с жизненным циклом Custom Elements.

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

Atomico позволяет встраивать компоненты друг в друга как обычные HTML-теги:

function App() {
    return html`
        <my-button label="Кликни"></my-button>
        <my-counter></my-counter>
    `;
}

customElements.define("app-root", component(App));
  • Компоненты автоматически инициализируются и управляют своим состоянием независимо.
  • Это обеспечивает модульность и повторное использование UI-блоков.

Обработка атрибутов и реактивность

При изменении атрибутов HTML компонент получает обновлённые значения автоматически:

const btn = document.querySelector("my-button");
btn.label = "Новое название"; // мгновенный ререндер
  • Atomico синхронизирует свойства компонента с атрибутами.
  • Любое изменение вызывает обновление рендера, сохраняя реактивность на уровне DOM.

Расширение стандартных элементов

Atomico поддерживает extends стандартных HTML-элементов:

function FancyInput({ value }) {
    return html`<input value=${value} />`;
}

customElements.define("fancy-input", component(FancyInput), { extends: "input" });
  • Элемент наследует все возможности базового <input>.
  • Можно комбинировать стандартное поведение с реактивной логикой Atomico.

Итоговое понимание

Custom Elements в Atomico — это сочетание стандарта Web Components и реактивной модели компонентов, предоставляющей:

  • Простое определение компонентов через функции.
  • Связь атрибутов и свойств с реактивным состоянием.
  • Инкапсуляцию разметки и стилей через Shadow DOM.
  • События и хуки для управления жизненным циклом.
  • Возможность композиции и расширения стандартных элементов.

Такой подход делает Atomico мощным инструментом для построения современных веб-приложений, сохраняя совместимость с нативным DOM и стандартами браузеров.