Использование компонента в HTML

Библиотека Haunted предоставляет минималистичный способ создания веб-компонентов на базе Web Components с реактивным состоянием, используя функциональный подход, схожий с React Hooks. Компоненты Haunted представляют собой обычные функции, возвращающие шаблоны HTML с помощью библиотеки lit-html.

Подключение Haunted

Для использования Haunted в проекте достаточно подключить библиотеку через CDN или установить через npm:

<script type="module">
  import { html, render } from 'https://unpkg.com/lit-html?module';
  import { component, useState, useEffect } from 'https://unpkg.com/haunted?module';
</script>

Или через npm:

npm install haunted lit-html
import { html, render } from 'lit-html';
import { component, useState, useEffect } from 'haunted';

Создание функционального компонента

Компонент Haunted — это функция, которая принимает объект props и возвращает шаблон html:

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

  return html`
    <div>
      <p>Счётчик: ${count}</p>
      <button @click=${() => setCount(count + 1)}>Увеличить</button>
      <button @click=${() => setCount(count - 1)}>Уменьшить</button>
    </div>
  `;
}

Ключевой особенностью является использование хуков useState для хранения состояния и useEffect для побочных эффектов. Компонент автоматически обновляется при изменении состояния.

Регистрация компонента в браузере

Чтобы компонент можно было использовать в HTML, его необходимо зарегистрировать как кастомный элемент:

customElements.define('my-counter', component(Counter));

После регистрации компонент можно использовать в HTML так же, как стандартный элемент:

<my-counter initial="10"></my-counter>

Особенности работы с атрибутами:

  • Атрибуты, переданные в компонент, попадают в объект props.
  • Все атрибуты передаются как строки, поэтому для числовых значений необходимо преобразование.
  • Изменение атрибута автоматически не обновляет состояние, если это не реализовано через реактивные хуки.

Встроенные хуки Haunted

Haunted предоставляет набор хуков, аналогичных React, для управления состоянием и жизненным циклом:

  • useState(initialValue) — создание локального состояния компонента.
  • useEffect(effect, deps) — выполнение побочных эффектов при изменении зависимостей.
  • useRef(initialValue) — создание ссылки на DOM-элемент или любое значение.
  • useContext(Context) — получение значения из контекста, если используется контекст.

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

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(interval); // очистка при размонтировании
  }, []);

  return html`<p>Прошло секунд: ${seconds}</p>`;
}

customElements.define('my-timer', component(Timer));

Использование слотов

Haunted поддерживает слоты, что позволяет вставлять произвольный HTML внутрь компонента:

function Card({ title }) {
  return html`
    <div class="card">
      <h2>${title}</h2>
      <slot></slot>
    </div>
  `;
}

customElements.define('my-card', component(Card));

Использование в HTML:

<my-card title="Пример карточки">
  <p>Содержимое карточки передано через слот.</p>
</my-card>

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

Стили можно подключать несколькими способами:

  1. Через стандартные CSS-стили: обычные селекторы будут работать, но стили будут глобальными.
  2. Через Shadow DOM: Haunted автоматически рендерит компонент в Shadow Root, поэтому локальные стили можно добавить через тег <style> внутри шаблона:
function StyledButton() {
  return html`
    <style>
      button {
        background-color: #3498db;
        color: white;
        border: none;
        padding: 10px 20px;
        cursor: pointer;
      }
      button:hover {
        background-color: #2980b9;
      }
    </style>
    <button>Нажми меня</button>
  `;
}

customElements.define('styled-button', component(StyledButton));

Взаимодействие с DOM и событиями

События добавляются через синтаксис @event=${handler}. Это работает как с пользовательскими событиями, так и с нативными:

function ClickLogger() {
  const handleClick = (e) => console.log('Клик:', e.target);

  return html`
    <button @click=${handleClick}>Логировать клик</button>
  `;
}

customElements.define('click-logger', component(ClickLogger));

Внутри обработчика можно изменять состояние компонента через хуки useState.

Передача и обновление данных

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

  • События: компонент может испускать кастомные события для передачи данных родителю.
  • Хуки состояния: изменения через useState автоматически перерисовывают компонент.

Пример кастомного события:

function InputEmitter() {
  const [value, setValue] = useState('');

  const emitChange = () => {
    const event = new CustomEvent('value-change', {
      detail: value,
      bubbles: true,
      composed: true
    });
    dispatchEvent(event);
  };

  return html`
    <input @input=${e => setValue(e.target.value)} />
    <button @click=${emitChange}>Отправить</button>
  `;
}

customElements.define('input-emitter', component(InputEmitter));

Использование в родительском элементе:

<input-emitter @value-change=${e => console.log(e.detail)}></input-emitter>

Итоговое поведение

Компоненты Haunted обеспечивают:

  • Реактивность без сложной инфраструктуры.
  • Локальное состояние через хуки.
  • Shadow DOM для инкапсуляции стилей.
  • Простую интеграцию с HTML через кастомные элементы и слоты.
  • Лёгкость тестирования, так как компоненты — обычные функции.

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